require 'puppet/indirector/face'
require 'puppet/node/facts'
require 'puppet/util/fact_dif'

EXCLUDE_LIST = %w[facterversion
  swapfree_mb swapsize_mb
  load_averages\.*
  memory\.swap\.available_bytes memory\.swap\.capacity memory\.swap\.total_bytes
  memory\.swap\.used_bytes memory\.swap\.available
  memory\.system\.available memory\.system\.available_bytes memory\.system\.capacity memory\.swap\.used
  memory\.system\.total_bytes memory\.system\.used memory\.system\.used_bytes
  memoryfree memoryfree_mb memorysize_mb
  mountpoints\..* mtu_.* mountpoints\..*\.capacity
  networking\.interfaces\..*\.mtu networking\.mtu partitions\..*\.filesystem
  partitions\..*\.size_bytes partitions\..*\.mount partitions\..*\.uuid
  disks\..*\.size_bytes
  hypervisors\.lpar\.partition_number hypervisors\.xen\.privileged hypervisors\.zone\..* hypervisors\.ldom\..*
  processors\.speed
  ldom_.*
  boardassettag dmi\.board\.asset_tag
  blockdevice_.*_vendor blockdevice_.*_size
  system_uptime\.days system_uptime\.hours system_uptime\.seconds system_uptime\.uptime
  uptime_days uptime_hours uptime_seconds
  system_profiler\.uptime
  sp_uptime
  uptime]

Puppet::Indirector::Face.define(:facts, '0.0.1') do
  copyright "Puppet Inc.", 2011
  license   _("Apache 2 license; see COPYING")

  summary _("Retrieve and store facts.")
  description <<-'EOT'
    This subcommand manages facts, which are collections of normalized system
    information used by Puppet. It can read facts directly from the local system
    (with the default `facter` terminus).
  EOT

  find = get_action(:find)
  find.summary _("Retrieve a node's facts.")
  find.arguments _("[<node_certname>]")
  find.returns <<-'EOT'
    A hash containing some metadata and (under the "values" key) the set
    of facts for the requested node. When used from the Ruby API: A
    Puppet::Node::Facts object.

    RENDERING ISSUES: Facts cannot currently be rendered as a string; use yaml
    or json.
  EOT
  find.notes <<-'EOT'
    When using the `facter` terminus, the host argument is ignored.
  EOT
  find.examples <<-'EOT'
    Get facts from the local system:

    $ puppet facts find
  EOT
  find.default = true

  deactivate_action(:destroy)
  deactivate_action(:search)

  action(:upload) do
    summary _("Upload local facts to the puppet master.")
    description <<-'EOT'
      Reads facts from the local system using the `facter` terminus, then
      saves the returned facts using the rest terminus.
    EOT
    returns "Nothing."
    notes <<-'EOT'
      This action requires that the puppet master's `auth.conf` file
      allow `PUT` or `save` access to the `/puppet/v3/facts` API endpoint.

      For details on configuring Puppet Server's `auth.conf`, see:

      <https://puppet.com/docs/puppetserver/latest/config_file_auth.html>

      For legacy Rack-based Puppet Masters, see:

      <https://puppet.com/docs/puppet/latest/config_file_auth.html>
    EOT
    examples <<-'EOT'
      Upload facts:

      $ puppet facts upload
    EOT

    render_as :json

    when_invoked do |options|
      # Use `agent` sections  settings for certificates, Puppet Server URL,
      # etc. instead of `user` section settings.
      Puppet.settings.preferred_run_mode = :agent
      Puppet::Node::Facts.indirection.terminus_class = :facter

      facts = Puppet::Node::Facts.indirection.find(Puppet[:node_name_value])
      unless Puppet[:node_name_fact].empty?
        Puppet[:node_name_value] = facts.values[Puppet[:node_name_fact]]
        facts.name = Puppet[:node_name_value]
      end

      client = Puppet.runtime[:http]
      session = client.create_session
      puppet = session.route_to(:puppet)

      Puppet.notice(_("Uploading facts for '%{node}' to '%{server}'") % {
                    node: Puppet[:node_name_value],
                    server: puppet.url.hostname})

      puppet.put_facts(Puppet[:node_name_value], facts: facts, environment: Puppet.lookup(:current_environment).name.to_s)
      nil
    end
  end

  action(:diff) do
    summary _("Compare Facter 3 output with Facter 4 output")
    description <<-'EOT'
    Compares output from facter 3 with Facter 4 and prints the differences
    EOT
    returns "Differences between Facter 3 and Facter 4 output as an array."
    notes <<-'EOT'
    EOT
    examples <<-'EOT'
    get differences between facter versions:
    $ puppet facts diff
    EOT

    render_as :json

    when_invoked do |*args|
      Puppet.settings.preferred_run_mode = :agent
      Puppet::Node::Facts.indirection.terminus_class = :facter

      if Puppet::Util::Package.versioncmp(Facter.value('facterversion'), '4.0.0') < 0
        facter3_result = Puppet::Node::Facts.indirection.find(Puppet.settings[:certname])
        begin
          require 'facter-ng'
          facter4_result = Puppet::Node::Facts.indirection.find(Puppet.settings[:certname])
        rescue LoadError
          raise ArgumentError, 'facter-ng could not be loaded'
        end
        fact_diff = FactDif.new(facter3_result.to_json, facter4_result.to_json, EXCLUDE_LIST)
        fact_diff.difs
      else
        Puppet.warning _("Already using Facter 4. To use `puppet facts diff` remove facterng from the .conf file or run `puppet config set facterng false`.")
        exit 0
      end
    end
  end
end
