require 'puppet/application'
require 'puppet/daemon'
require 'puppet/util/pidlock'

class Puppet::Application::Master < Puppet::Application

  run_mode :master

  option("--debug", "-d")
  option("--verbose", "-v")

  # internal option, only to be used by ext/rack/config.ru
  option("--rack")

  option("--compile host",  "-c host") do |arg|
    options[:node] = arg
  end

  option("--logdest DEST",  "-l DEST") do |arg|
    handle_logdest_arg(arg)
  end

  option("--parseonly") do |args|
    puts "--parseonly has been removed. Please use 'puppet parser validate <manifest>'"
    exit 1
  end

  def summary
    _("The puppet master daemon")
  end

  def help
    <<-HELP

puppet-master(8) -- #{summary}
========

SYNOPSIS
--------
The central puppet server. Functions as a certificate authority by
default.


USAGE
-----
puppet master [-D|--daemonize|--no-daemonize] [-d|--debug] [-h|--help]
  [-l|--logdest syslog|<FILE>|console] [-v|--verbose] [-V|--version]
  [--compile <NODE-NAME>]


DESCRIPTION
-----------
This command starts an instance of puppet master, running as a daemon
and using Ruby's built-in Webrick webserver. Puppet master can also be
managed by other application servers; when this is the case, this
executable is not used.


OPTIONS
-------

Note that any Puppet setting that's valid in the configuration file is also a
valid long argument. For example, 'server' is a valid setting, so you can
specify '--server <servername>' as an argument. Boolean settings translate into
'--setting' and '--no-setting' pairs.

See the configuration file documentation at
https://puppet.com/docs/puppet/latest/configuration.html for the
full list of acceptable settings. A commented list of all settings can also be
generated by running puppet master with '--genconfig'.

* --daemonize:
  Send the process into the background. This is the default.
  (This is a Puppet setting, and can go in puppet.conf. Note the special 'no-'
  prefix for boolean settings on the command line.)

* --no-daemonize:
  Do not send the process into the background.
  (This is a Puppet setting, and can go in puppet.conf. Note the special 'no-'
  prefix for boolean settings on the command line.)

* --debug:
  Enable full debugging.

* --help:
  Print this help message.

* --logdest:
  Where to send log messages. Choose between 'syslog' (the POSIX syslog
  service), 'console', or the path to a log file. If debugging or verbosity is
  enabled, this defaults to 'console'. Otherwise, it defaults to 'syslog'.

  A path ending with '.json' will receive structured output in JSON format. The
  log file will not have an ending ']' automatically written to it due to the
  appending nature of logging. It must be appended manually to make the content
  valid JSON.

* --masterport:
  The port on which to listen for traffic. The default port is 8140.
  (This is a Puppet setting, and can go in puppet.conf.)

* --verbose:
  Enable verbosity.

* --version:
  Print the puppet version number and exit.

* --compile:
  Compile a catalogue and output it in JSON from the puppet master. Uses
  facts contained in the $vardir/yaml/ directory to compile the catalog.


EXAMPLE
-------
  puppet master

DIAGNOSTICS
-----------

When running as a standalone daemon, puppet master accepts the
following signals:

* SIGHUP:
  Restart the puppet master server.
* SIGINT and SIGTERM:
  Shut down the puppet master server.
* SIGUSR2:
  Close file descriptors for log files and reopen them. Used with logrotate.

AUTHOR
------
Luke Kanies


COPYRIGHT
---------
Copyright (c) 2012 Puppet Inc., LLC Licensed under the Apache 2.0 License

    HELP
  end

  def app_defaults
    super.merge({
      :facts_terminus => 'yaml'
    })
  end

  def preinit
    Signal.trap(:INT) do
      $stderr.puts _("Canceling startup")
      exit(0)
    end

    # save ARGV to protect us from it being smashed later by something
    @argv = ARGV.dup
  end

  def run_command
    if options[:node]
      compile
    else
      main
    end
  end

  def compile
    begin
      unless catalog = Puppet::Resource::Catalog.indirection.find(options[:node])
        raise _("Could not compile catalog for %{node}") % { node: options[:node] }
      end

      puts JSON::pretty_generate(catalog.to_resource, :allow_nan => true, :max_nesting => false)
    rescue => detail
      Puppet.log_exception(detail, _("Failed to compile catalog for node %{node}: %{detail}") % { node: options[:node], detail: detail })
      exit(30)
    end
    exit(0)
  end

  def main
    require 'etc'
    # Make sure we've got a localhost ssl cert
    Puppet::SSL::Host.localhost

    # And now configure our server to *only* hit the CA for data, because that's
    # all it will have write access to.
    Puppet::SSL::Host.ca_location = :only if Puppet::SSL::CertificateAuthority.ca?

    if Puppet.features.root?
      if Puppet::Type.type(:user).new(:name => Puppet[:user]).exists?
        begin
          Puppet::Util.chuser
        rescue => detail
          Puppet.log_exception(detail, _("Could not change user to %{user}: %{detail}") % { user: Puppet[:user], detail: detail })
          exit(39)
        end
      else
        Puppet.err(_("Could not change user to %{user}. User does not exist and is required to continue.") % { user: Puppet[:user] })
        exit(74)
      end
    end

    if options[:rack]
      Puppet.deprecation_warning(_("The Rack Puppet master server is deprecated and will be removed in a future release. Please use Puppet Server instead. See http://links.puppet.com/deprecate-rack-webrick-servers for more information."))
      start_rack_master
    else
      Puppet.deprecation_warning(_("The WEBrick Puppet master server is deprecated and will be removed in a future release. Please use Puppet Server instead. See http://links.puppet.com/deprecate-rack-webrick-servers for more information."))
      start_webrick_master
    end
  end

  def setup_logs
    set_log_level
    handle_logdest_arg(Puppet[:logdest])

    unless options[:setdest]
      if options[:node]
        # We are compiling a catalog for a single node with '--compile' and a
        # logging destination has not already been explicitly specified.
        Puppet::Util::Log.newdestination(:console)
      elsif !(Puppet[:daemonize] or options[:rack])
        # We are running a webrick master which has been explicitly foregrounded
        # and a logging destination has not already been explicitly specified,
        # assume users want to see logging and log to the console.
        Puppet::Util::Log.newdestination(:console)
      else
        # No explicit log destination has been given with '--logdest', or via settings,
        # and we're either a daemonized webrick master or running under rack, log to
        # syslog.
        Puppet::Util::Log.newdestination(:syslog)
      end
    end
  end

  def setup_terminuses
    require 'puppet/file_serving/content'
    require 'puppet/file_serving/metadata'

    Puppet::FileServing::Content.indirection.terminus_class = :file_server
    Puppet::FileServing::Metadata.indirection.terminus_class = :file_server

    Puppet::FileBucket::File.indirection.terminus_class = :file
  end

  def setup_ssl
    # Configure all of the SSL stuff.
    if Puppet::SSL::CertificateAuthority.ca?
      Puppet::SSL::Host.ca_location = :local
      Puppet.settings.use :ca
      Puppet::SSL::CertificateAuthority.instance
    else
      Puppet::SSL::Host.ca_location = :none
    end
    Puppet::SSL::Oids.register_puppet_oids
    Puppet::SSL::Oids.load_custom_oid_file(Puppet[:trusted_oid_mapping_file])
  end

  # Honor the :node_cache_terminus setting if users have specified it directly.
  # We normally want this nil as use-cases for querying nodes should be going to
  # PuppetDB.
  # @see PUP-6060
  # @return [void]
  def setup_node_cache
    Puppet::Node.indirection.cache_class = Puppet[:node_cache_terminus]
  end

  def setup
    raise Puppet::Error.new(_("Puppet master is not supported on Microsoft Windows")) if Puppet.features.microsoft_windows?

    setup_logs

    exit(Puppet.settings.print_configs ? 0 : 1) if Puppet.settings.print_configs?

    Puppet.settings.use :main, :master, :ssl, :metrics

    setup_terminuses

    setup_node_cache

    setup_ssl
  end

  private

  # Start a master that will be using WeBrick.
  #
  # This method will block until the master exits.
  def start_webrick_master
    require 'puppet/network/server'
    daemon = Puppet::Daemon.new(Puppet::Util::Pidlock.new(Puppet[:pidfile]))

    daemon.argv = @argv
    daemon.server = Puppet::Network::Server.new(Puppet[:bindaddress], Puppet[:masterport])
    daemon.daemonize if Puppet[:daemonize]

    # Setup signal traps immediately after daemonization so we clean up the daemon
    daemon.set_signal_traps

    announce_start_of_master

    daemon.start
  end

  # Start a master that will be used for a Rack container.
  #
  # This method immediately returns the Rack handler that must be returned to
  # the calling Rack container
  def start_rack_master
    require 'puppet/network/http/rack'

    announce_start_of_master

    return Puppet::Network::HTTP::Rack.new()
  end

  def announce_start_of_master
    Puppet.notice _("Starting Puppet master version %{version}") % { version: Puppet.version }
  end
end
