require 'puppet/application'

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|
    begin
      Puppet::Util::Log.newdestination(arg)
      options[:setdest] = true
    rescue => detail
      Puppet.log_exception(detail)
    end
  end

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

  def help
    <<-'HELP'

puppet-master(8) -- The puppet master daemon
========

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 <file>|console|syslog] [-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 configuration parameter that's valid in the configuration
file is also a valid long argument. For example, 'ssldir' is a valid
configuration parameter, so you can specify '--ssldir <directory>' as an
argument.

See the configuration file documentation at
http://docs.puppetlabs.com/references/stable/configuration.html for the
full list of acceptable parameters. A commented list of all
configuration options can also be generated by running puppet master
with '--genconfig'.

* --daemonize:
  Send the process into the background. This is the default.

* --no-daemonize:
  Do not send the process into the background.

* --debug:
  Enable full debugging.

* --help:
  Print this help message.

* --logdest:
  Where to send messages. Choose between syslog, the console, and a log
  file. Defaults to sending messages to syslog, or the console if
  debugging or verbosity is enabled.

* --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 Labs, LLC Licensed under the Apache 2.0 License

    HELP
  end

  # Sets up the 'node_cache_terminus' default to use the Write Only Yaml terminus :write_only_yaml.
  # If this is not wanted, the setting ´node_cache_terminus´ should be set to nil.
  # @see Puppet::Node::WriteOnlyYaml
  # @see #setup_node_cache
  # @see puppet issue 16753
  #
  def app_defaults
    super.merge({
      :node_cache_terminus => :write_only_yaml,
      :facts_terminus => 'yaml'
    })
  end

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

    # Create this first-off, so we have ARGV
    require 'puppet/daemon'
    @daemon = Puppet::Daemon.new
    @daemon.argv = ARGV.dup
  end

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

  def compile
    Puppet::Util::Log.newdestination :console
    begin
      unless catalog = Puppet::Resource::Catalog.indirection.find(options[:node])
        raise "Could not compile catalog for #{options[:node]}"
      end

      jj catalog.to_resource
    rescue => detail
      $stderr.puts 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?
      begin
        Puppet::Util.chuser
      rescue => detail
        Puppet.log_exception(detail, "Could not change user to #{Puppet[:user]}: #{detail}")
        exit(39)
      end
    end

    unless options[:rack]
      require 'puppet/network/server'
      @daemon.server = Puppet::Network::Server.new(Puppet[:bindaddress], Puppet[:masterport])
      @daemon.daemonize if Puppet[:daemonize]
    else
      require 'puppet/network/http/rack'
      @app = Puppet::Network::HTTP::Rack.new()
    end

    Puppet.notice "Starting Puppet master version #{Puppet.version}"

    unless options[:rack]
      @daemon.start
    else
      return @app
    end
  end

  def setup_logs
    # Handle the logging settings.
    if options[:debug] or options[:verbose]
      if options[:debug]
        Puppet::Util::Log.level = :debug
      else
        Puppet::Util::Log.level = :info
      end

      unless Puppet[:daemonize] or options[:rack]
        Puppet::Util::Log.newdestination(:console)
        options[:setdest] = true
      end
    end

    Puppet::Util::Log.newdestination(:syslog) unless options[:setdest]
  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
  end

  # Sets up a special node cache "write only yaml" that collects and stores node data in yaml
  # but never finds or reads anything (this since a real cache causes stale data to be served
  # in circumstances when the cache can not be cleared).
  # @see puppet issue 16753
  # @see Puppet::Node::WriteOnlyYaml
  # @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
end
