# == Class: elasticsearch
#
# This class is able to install or remove elasticsearch on a node.
# It manages the status of the related service.
#
# === Parameters
#
# [*ensure*]
#   String. Controls if the managed resources shall be <tt>present</tt> or
#   <tt>absent</tt>. If set to <tt>absent</tt>:
#   * The managed software packages are being uninstalled.
#   * Any traces of the packages will be purged as good as possible. This may
#     include existing configuration files. The exact behavior is provider
#     dependent. Q.v.:
#     * Puppet type reference: {package, "purgeable"}[http://j.mp/xbxmNP]
#     * {Puppet's package provider source code}[http://j.mp/wtVCaL]
#   * System modifications (if any) will be reverted as good as possible
#     (e.g. removal of created users, services, changed log settings, ...).
#   * This is thus destructive and should be used with care.
#   Defaults to <tt>present</tt>.
#
# [*autoupgrade*]
#   Boolean. If set to <tt>true</tt>, any managed package gets upgraded
#   on each Puppet run when the package provider is able to find a newer
#   version than the present one. The exact behavior is provider dependent.
#   Q.v.:
#   * Puppet type reference: {package, "upgradeable"}[http://j.mp/xbxmNP]
#   * {Puppet's package provider source code}[http://j.mp/wtVCaL]
#   Defaults to <tt>false</tt>.
#
# [*status*]
#   String to define the status of the service. Possible values:
#   * <tt>enabled</tt>: Service is running and will be started at boot time.
#   * <tt>disabled</tt>: Service is stopped and will not be started at boot
#     time.
#   * <tt>running</tt>: Service is running but will not be started at boot time.
#     You can use this to start a service on the first Puppet run instead of
#     the system startup.
#   * <tt>unmanaged</tt>: Service will not be started at boot time and Puppet
#     does not care whether the service is running or not. For example, this may
#     be useful if a cluster management software is used to decide when to start
#     the service plus assuring it is running on the desired node.
#   Defaults to <tt>enabled</tt>. The singular form ("service") is used for the
#   sake of convenience. Of course, the defined status affects all services if
#   more than one is managed (see <tt>service.pp</tt> to check if this is the
#   case).
#
# [*version*]
#   String to set the specific version you want to install.
#   Defaults to <tt>false</tt>.
#
# [*restart_on_change*]
#   Boolean that determines if the application should be automatically restarted
#   whenever the configuration, package, or plugins change. Enabling this
#   setting will cause Elasticsearch to restart whenever there is cause to
#   re-read configuration files, load new plugins, or start the service using an
#   updated/changed executable. This may be undesireable in highly available
#   environments.
#
#   If all other restart_* parameters are left unset, the value of
#   restart_on_change is used for all other restart_*_change defaults.
#
#   Defaults to <tt>false</tt>, which disables automatic restarts. Setting to
#   <tt>true</tt> will restart the application on any config, plugin, or
#   package change.
#
# [*restart_config_change*]
#   Boolean that determines if the application should be automatically restarted
#   whenever the configuration changes. This includes the Elasticsearch
#   configuration file, any service files, and defaults files.
#   Disabling automatic restarts on config changes may be desired in an
#   environment where you need to ensure restarts occur in a controlled/rolling
#   manner rather than during a Puppet run.
#
#   Defaults to <tt>undef</tt>, in which case the default value of
#   restart_on_change will be used (defaults to false).
#
# [*restart_package_change*]
#   Boolean that determines if the application should be automatically restarted
#   whenever the package (or package version) for Elasticsearch changes.
#   Disabling automatic restarts on package changes may be desired in an
#   environment where you need to ensure restarts occur in a controlled/rolling
#   manner rather than during a Puppet run.
#
#   Defaults to <tt>undef</tt>, in which case the default value of
#   restart_on_change will be used (defaults to false).
#
# [*restart_plugin_change*]
#   Boolean that determines if the application should be automatically restarted
#   whenever plugins are installed or removed.
#   Disabling automatic restarts on plugin changes may be desired in an
#   environment where you need to ensure restarts occur in a controlled/rolling
#   manner rather than during a Puppet run.
#
#   Defaults to <tt>undef</tt>, in which case the default value of
#   restart_on_change will be used (defaults to false).
#
# [*configdir*]
#   Path to directory containing the elasticsearch configuration.
#   Use this setting if your packages deviate from the norm (/etc/elasticsearch)
#
# [*plugindir*]
#   Path to directory containing the elasticsearch plugins
#   Use this setting if your packages deviate from the norm (/usr/share/elasticsearch/plugins)
#
# [*package_url*]
#   Url to the package to download.
#   This can be a http,https or ftp resource for remote packages
#   puppet:// resource or file:/ for local packages
#
# [*package_provider*]
#   Way to install the packages, currently only packages are supported.
#
# [*package_dir*]
#   Directory where the packages are downloaded to
#
# [*package_name*]
#   Name of the package to install
#
# [*purge_package_dir*]
#   Purge package directory on removal
#
# [*package_dl_timeout*]
#   For http,https and ftp downloads you can set howlong the exec resource may take.
#   Defaults to: 600 seconds
#
# [*proxy_url*]
#   For http and https downloads you can set a proxy server to use
#   Format: proto://[user:pass@]server[:port]/
#   Defaults to: undef (proxy disabled)
#
# [*elasticsearch_user*]
#   The user Elasticsearch should run as. This also sets the file rights.
#
# [*elasticsearch_group*]
#   The group Elasticsearch should run as. This also sets the file rights
#
# [*purge_configdir*]
#   Purge the config directory for any unmanaged files
#
# [*service_provider*]
#   Service provider to use. By Default when a single service provider is possibe that one is selected.
#
# [*init_defaults*]
#   Defaults file content in hash representation
#
# [*init_defaults_file*]
#   Defaults file as puppet resource
#
# [*init_template*]
#   Service file as a template
#
# [*config*]
#   Elasticsearch configuration hash
#
# [*config_hiera_merge*]
#   Enable Hiera merging for the config hash
#   Defaults to: false
#
# [*datadir*]
#   Allows you to set the data directory of Elasticsearch
#
# [*logdir*]
#   Use different directory for logging
#
# [*java_install*]
#  Install java which is required for Elasticsearch.
#  Defaults to: false
#
# [*java_package*]
#   If you like to install a custom java package, put the name here.
#
# [*manage_repo*]
#   Enable repo management by enabling our official repositories
#
# [*repo_version*]
#   Our repositories are versioned per major version (0.90, 1.0) select here which version you want
#
# [*repo_priority*]
#   Repository priority. yum and apt supported.
#   Default: undef
#
# [*repo_key_id*]
#   String.  The apt GPG key id
#   Default: 46095ACC8548582C1A2699A9D27D666CD88E42B4
#
# [*repo_key_source*]
#   String.  URL of the apt GPG key
#   Default: http://packages.elastic.co/GPG-KEY-elasticsearch
#
# [*repo_proxy*]
#   String.  URL for repository proxy
#   Default: undef
#
# [*logging_config*]
#   Hash representation of information you want in the logging.yml file
#
# [*logging_file*]
#   Instead of a hash you can supply a puppet:// file source for the logging.yml file
#
# [*logging_template*]
#  Use a custom logging template - just supply the reative path ie ${module}/elasticsearch/logging.yml.erb
#
# [*default_logging_level*]
#   Default logging level for Elasticsearch.
#   Defaults to: INFO
#
# [*repo_stage*]
#   Use stdlib stage setup for managing the repo, instead of anchoring
#
# [*instances*]
#   Define instances via a hash. This is mainly used with Hiera's auto binding
#   Defaults to: undef
#
# [*instances_hiera_merge*]
#   Enable Hiera's merging function for the instances
#   Defaults to: false
#
# [*plugins*]
#   Define plugins via a hash. This is mainly used with Hiera's auto binding
#   Defaults to: undef
#
# [*plugins_hiera_merge*]
#   Enable Hiera's merging function for the plugins
#   Defaults to: false
#
# [*package_pin*]
#   Enables package version pinning.
#   This pins the package version to the set version number and avoids
#   package upgrades.
#   Defaults to: true
#
# [*use_ssl*]
#   Enable auth on api calls. This parameter is deprecated in favor of setting
#   the `api_protocol` parameter to "https".
#   Defaults to: false
#   This variable is deprecated
#
# [*validate_ssl*]
#   Enable ssl validation on api calls. This parameter is deprecated in favor
#   of the `validate_tls` parameter.
#   Defaults to: true
#   This variable is deprecated
#
# [*ssl_user*]
#   Defines the username for authentication. This parameter is deprecated in
#   favor of the `api_basic_auth_username` parameter.
#   Defaults to: undef
#   This variable is deprecated
#
# [*ssl_password*]
#   Defines the password for authentication. This parameter is deprecated in
#   favor of the `api_basic_auth_password` parameter.
#   Defaults to: undef
#   This variable is deprecated
#
# [*api_protocol*]
#   Default protocol to use when accessing Elasticsearch APIs.
#   Defaults to: http
#
# [*api_host*]
#   Default host to use when accessing Elasticsearch APIs.
#   Defaults to: localhost
#
# [*api_port*]
#   Default port to use when accessing Elasticsearch APIs.
#   Defaults to: 9200
#
# [*api_timeout*]
#   Default timeout (in seconds) to use when accessing Elasticsearch APIs.
#   Defaults to: 10
#
# [*validate_tls*]
#   Enable TLS/SSL validation on API calls.
#   Defaults to: true
#
# [*api_basic_auth_username*]
#   Defines the default REST basic auth username for API authentication.
#   Defaults to: undef
#
# [*api_basic_auth_password*]
#   Defines the default REST basic auth password for API authentication.
#   Defaults to: undef
#
# [*api_ca_file*]
#   Path to a CA file which will be used to validate server certs when
#   communicating with the Elasticsearch API over HTTPS.
#   Defaults to: undef
#
# [*api_ca_path*]
#   Path to a directory with CA files which will be used to validate server
#   certs when communicating with the Elasticsearch API over HTTPS.
#   Defaults to: undef
#
# [*system_key*]
#   Source for the Shield system key. Valid values are any that are
#   supported for the file resource `source` parameter.
#   Value type is string
#   Default value: undef
#
# [*file_rolling_type*]
#   Configuration for the file appender rotation. It can be 'dailyRollingFile'
#   or 'rollingFile'. The first rotates by name, and the second one by size.
#   Value type is string
#   Default value: dailyRollingFile
#
# [*daily_rolling_date_pattern*]
#   File pattern for the file appender log when file_rolling_type is 'dailyRollingFile'
#   Value type is string
#   Default value: "'.'yyyy-MM-dd"
#
# [*rolling_file_max_backup_index*]
#   Max number of logs to store whern file_rolling_type is 'rollingFile'
#   Value type is integer
#   Default value: 1
#
# [*rolling_file_max_file_size*]
#   Max log file size when file_rolling_type is 'rollingFile'
#   Value type is string
#   Default value: 10MB
#
# The default values for the parameters are set in elasticsearch::params. Have
# a look at the corresponding <tt>params.pp</tt> manifest file if you need more
# technical information about them.
#
# === Examples
#
# * Installation, make sure service is running and will be started at boot time:
#     class { 'elasticsearch': }
#
# * Removal/decommissioning:
#     class { 'elasticsearch':
#       ensure => 'absent',
#     }
#
# * Install everything but disable service(s) afterwards
#     class { 'elasticsearch':
#       status => 'disabled',
#     }
#
#
# === Authors
#
# * Richard Pijnenburg <mailto:richard.pijnenburg@elasticsearch.com>
#
class elasticsearch(
  $ensure                         = $elasticsearch::params::ensure,
  $status                         = $elasticsearch::params::status,
  $restart_on_change              = $elasticsearch::params::restart_on_change,
  $restart_config_change          = $elasticsearch::restart_on_change,
  $restart_package_change         = $elasticsearch::restart_on_change,
  $restart_plugin_change          = $elasticsearch::restart_on_change,
  $autoupgrade                    = $elasticsearch::params::autoupgrade,
  $version                        = false,
  $package_provider               = 'package',
  $package_url                    = undef,
  $package_dir                    = $elasticsearch::params::package_dir,
  $package_name                   = $elasticsearch::params::package,
  $package_pin                    = true,
  $purge_package_dir              = $elasticsearch::params::purge_package_dir,
  $package_dl_timeout             = $elasticsearch::params::package_dl_timeout,
  $proxy_url                      = undef,
  $elasticsearch_user             = $elasticsearch::params::elasticsearch_user,
  $elasticsearch_group            = $elasticsearch::params::elasticsearch_group,
  $configdir                      = $elasticsearch::params::configdir,
  $purge_configdir                = $elasticsearch::params::purge_configdir,
  $service_provider               = 'init',
  $init_defaults                  = undef,
  $init_defaults_file             = undef,
  $init_template                  = "${module_name}/etc/init.d/${elasticsearch::params::init_template}",
  $config                         = undef,
  $config_hiera_merge             = false,
  $datadir                        = $elasticsearch::params::datadir,
  $logdir                         = $elasticsearch::params::logdir,
  $plugindir                      = $elasticsearch::params::plugindir,
  $java_install                   = false,
  $java_package                   = undef,
  $manage_repo                    = false,
  $repo_version                   = undef,
  $repo_priority                  = undef,
  $repo_key_id                    = '46095ACC8548582C1A2699A9D27D666CD88E42B4',
  $repo_key_source                = 'https://artifacts.elastic.co/GPG-KEY-elasticsearch',
  $repo_proxy                     = undef,
  $logging_file                   = undef,
  $logging_config                 = undef,
  $logging_template               = undef,
  $default_logging_level          = $elasticsearch::params::default_logging_level,
  $repo_stage                     = false,
  $instances                      = undef,
  $instances_hiera_merge          = false,
  $plugins                        = undef,
  $plugins_hiera_merge            = false,
  $use_ssl                        = undef,
  $validate_ssl                   = undef,
  $ssl_user                       = undef,
  $ssl_password                   = undef,
  $api_protocol                   = 'http',
  $api_host                       = 'localhost',
  $api_port                       = 9200,
  $api_timeout                    = 10,
  $api_basic_auth_username        = undef,
  $api_basic_auth_password        = undef,
  $api_ca_file                    = undef,
  $api_ca_path                    = undef,
  $validate_tls                   = true,
  $system_key                     = undef,
  $file_rolling_type              = $elasticsearch::params::file_rolling_type,
  $daily_rolling_date_pattern     = $elasticsearch::params::daily_rolling_date_pattern,
  $rolling_file_max_backup_index  = $elasticsearch::params::rolling_file_max_backup_index,
  $rolling_file_max_file_size     = $elasticsearch::params::rolling_file_max_file_size,
) inherits elasticsearch::params {

  anchor {'elasticsearch::begin': }


  #### Validate parameters

  # ensure
  if ! ($ensure in [ 'present', 'absent' ]) {
    fail("\"${ensure}\" is not a valid ensure parameter value")
  }

  # autoupgrade
  validate_bool($autoupgrade)

  # service status
  if ! ($status in [ 'enabled', 'disabled', 'running', 'unmanaged' ]) {
    fail("\"${status}\" is not a valid status parameter value")
  }

  if ! ($file_rolling_type in [ 'dailyRollingFile', 'rollingFile']) {
    file("\"${file_rolling_type}\" is not a valid type")
  }

  validate_integer($rolling_file_max_backup_index)
  validate_string($daily_rolling_date_pattern)
  validate_string($rolling_file_max_file_size)


  # restart on change
  validate_bool(
    $restart_on_change,
    $restart_config_change,
    $restart_package_change,
    $restart_plugin_change
  )

  # purge conf dir
  validate_bool($purge_configdir)

  if is_array($elasticsearch::params::service_providers) {
    # Verify the service provider given is in the array
    if ! ($service_provider in $elasticsearch::params::service_providers) {
      fail("\"${service_provider}\" is not a valid provider for \"${::operatingsystem}\"")
    }
    $real_service_provider = $service_provider
  } else {
    # There is only one option so simply set it
    $real_service_provider = $elasticsearch::params::service_providers
  }

  if ($package_url != undef and $version != false) {
    fail('Unable to set the version number when using package_url option.')
  }

  if $ensure == 'present' {
    # validate config hash
    if ($config != undef) {
      validate_hash($config)
    }

    if ($logging_config != undef) {
      validate_hash($logging_config)
    }
  }

  # java install validation
  validate_bool($java_install)

  validate_bool(
    $manage_repo,
    $package_pin
  )

  if ($manage_repo == true and $ensure == 'present') {
    if $repo_version == undef {
      fail('Please fill in a repository version at $repo_version')
    } else {
      validate_string($repo_version)
    }
  }

  if ($version != false) {
    case $::osfamily {
      'RedHat', 'Linux', 'Suse': {
        if ($version =~ /.+-\d/) {
          $pkg_version = $version
        } else {
          $pkg_version = "${version}-1"
        }
      }
      default: {
        $pkg_version = $version
      }
    }
  }

  # Various parameters governing API access to Elasticsearch, handling
  # deprecated params.
  validate_string($api_protocol, $api_host)
  if $use_ssl != undef {
    validate_bool($use_ssl)
    warning('"use_ssl" parameter is deprecated; set $api_protocol to "https" instead')
    $_api_protocol = 'https'
  } else {
    $_api_protocol = $api_protocol
  }

  validate_bool($validate_tls)
  if $validate_ssl != undef {
    validate_bool($validate_ssl)
    warning('"validate_ssl" parameter is deprecated; use $validate_tls instead')
    $_validate_tls = $validate_ssl
  } else {
    $_validate_tls = $validate_tls
  }

  if $api_basic_auth_username { validate_string($api_basic_auth_username) }
  if $ssl_user != undef {
    validate_string($ssl_user)
    warning('"ssl_user" parameter is deprecated; use $api_basic_auth_username instead')
    $_api_basic_auth_username = $ssl_user
  } else {
    $_api_basic_auth_username = $api_basic_auth_username
  }

  if $api_basic_auth_password { validate_string($api_basic_auth_password) }
  if $ssl_password != undef {
    validate_string($ssl_password)
    warning('"ssl_password" parameter is deprecated; use $api_basic_auth_password instead')
    $_api_basic_auth_password = $ssl_password
  } else {
    $_api_basic_auth_password = $api_basic_auth_password
  }

  if ! is_integer($api_timeout) {
    fail("'${api_timeout}' is not an integer")
  }

  if ! is_integer($api_port) {
    fail("'${api_port}' is not an integer")
  }

  if $system_key != undef { validate_string($system_key) }

  #### Manage actions

  # package(s)
  class { 'elasticsearch::package': }

  # configuration
  class { 'elasticsearch::config': }

  # Hiera support for configuration hash
  validate_bool($config_hiera_merge)

  if $config_hiera_merge == true {
    $x_config = hiera_hash('elasticsearch::config', $config)
  } else {
    $x_config = $config
  }

  # Hiera support for instances
  validate_bool($instances_hiera_merge)

  if $instances_hiera_merge == true {
    $x_instances = hiera_hash('elasticsearch::instances', $::elasticsearch::instances)
  } else {
    $x_instances = $instances
  }

  if $x_instances {
    validate_hash($x_instances)
    create_resources('elasticsearch::instance', $x_instances)
  }

  # Hiera support for plugins
  validate_bool($plugins_hiera_merge)

  if $plugins_hiera_merge == true {
    $x_plugins = hiera_hash('elasticsearch::plugins', $::elasticsearch::plugins)
  } else {
    $x_plugins = $plugins
  }

  if $x_plugins {
    validate_hash($x_plugins)
    create_resources('elasticsearch::plugin', $x_plugins)
  }


  if $java_install == true {
    # Install java
    class { '::java':
      package      => $java_package,
      distribution => 'jre',
    }

    # ensure we first install java, the package and then the rest
    Anchor['elasticsearch::begin']
    -> Class['::java']
    -> Class['elasticsearch::package']
  }

  if $package_pin {
    class { 'elasticsearch::package::pin':
      before => Class['elasticsearch::package'],
    }
  }

  if ($manage_repo == true) {

    if ($repo_stage == false) {
      # use anchor for ordering

      # Set up repositories
      class { 'elasticsearch::repo': }

      # Ensure that we set up the repositories before trying to install
      # the packages
      Anchor['elasticsearch::begin']
      -> Class['elasticsearch::repo']
      -> Class['elasticsearch::package']

    } else {
      # use staging for ordering

      if !(defined(Stage[$repo_stage])) {
        stage { $repo_stage:  before => Stage['main'] }
      }

      class { 'elasticsearch::repo':
        stage => $repo_stage,
      }
    }

    if defined(Class['elasticsearch::package::pin']) {
      Class['elasticsearch::package::pin']
      -> Class['elasticsearch::repo']
    }

  }

  #### Manage relationships
  #
  # Note that many of these overly verbose declarations work around
  # https://tickets.puppetlabs.com/browse/PUP-1410
  # which means clean arrow order chaining won't work if someone, say,
  # doesn't declare any plugins.
  #
  # forgive me for what you're about to see

  if $ensure == 'present' {

    # Anchor, installation, and configuration
    Anchor['elasticsearch::begin']
    -> Class['elasticsearch::package']
    -> Class['elasticsearch::config']

    # Top-level ordering bindings for resources.
    Class['elasticsearch::config']
    -> Elasticsearch::Plugin <| ensure == 'present' or ensure == 'installed' |>
    Elasticsearch::Plugin <| ensure == 'absent' |>
    -> Class['elasticsearch::config']
    Class['elasticsearch::config']
    -> Elasticsearch::Instance <| |>
    Class['elasticsearch::config']
    -> Elasticsearch::Shield::User <| |>
    Class['elasticsearch::config']
    -> Elasticsearch::Shield::Role <| |>
    Class['elasticsearch::config']
    -> Elasticsearch::Template <| |>

  } else {

    # Main anchor and included classes
    Anchor['elasticsearch::begin']
    -> Class['elasticsearch::config']
    -> Class['elasticsearch::package']

    # Top-level ordering bindings for resources.
    Anchor['elasticsearch::begin']
    -> Elasticsearch::Plugin <| |>
    -> Class['elasticsearch::config']
    Anchor['elasticsearch::begin']
    -> Elasticsearch::Instance <| |>
    -> Class['elasticsearch::config']
    Anchor['elasticsearch::begin']
    -> Elasticsearch::Shield::User <| |>
    -> Class['elasticsearch::config']
    Anchor['elasticsearch::begin']
    -> Elasticsearch::Shield::Role <| |>
    -> Class['elasticsearch::config']
    Anchor['elasticsearch::begin']
    -> Elasticsearch::Template <| |>
    -> Class['elasticsearch::config']

  }

  # Install plugins before managing instances or shield users/roles
  Elasticsearch::Plugin <| ensure == 'present' or ensure == 'installed' |>
  -> Elasticsearch::Instance <| |>
  Elasticsearch::Plugin <| ensure == 'present' or ensure == 'installed' |>
  -> Elasticsearch::Shield::User <| |>
  Elasticsearch::Plugin <| ensure == 'present' or ensure == 'installed' |>
  -> Elasticsearch::Shield::Role <| |>

  # Remove plugins after managing shield users/roles
  Elasticsearch::Shield::User <| |>
  -> Elasticsearch::Plugin <| ensure == 'absent' |>
  Elasticsearch::Shield::Role <| |>
  -> Elasticsearch::Plugin <| ensure == 'absent' |>

  # Ensure roles are defined before managing users that reference roles
  Elasticsearch::Shield::Role <| |>
  -> Elasticsearch::Shield::User <| ensure == 'present' |>
  # Ensure users are removed before referenced roles are managed
  Elasticsearch::Shield::User <| ensure == 'absent' |>
  -> Elasticsearch::Shield::Role <| |>

  # Ensure users and roles are managed before calling out to templates
  Elasticsearch::Shield::Role <| |>
  -> Elasticsearch::Template <| |>
  Elasticsearch::Shield::User <| |>
  -> Elasticsearch::Template <| |>

  # Manage users/roles before instances (req'd to keep shield dir in sync)
  Elasticsearch::Shield::Role <| |>
  -> Elasticsearch::Instance <| |>
  Elasticsearch::Shield::User <| |>
  -> Elasticsearch::Instance <| |>

  # Ensure instances are started before managing templates
  Elasticsearch::Instance <| ensure == 'present' |>
  -> Elasticsearch::Template <| |>
  # Ensure instances are stopped after managing templates
  Elasticsearch::Template <| |>
  -> Elasticsearch::Instance <| ensure == 'absent' |>
}
