# Chef Provider for installing or removing Elasticsearch from package or tarball
# downloaded from elasticsearch.org and installed by package manager or ark resource
class ElasticsearchCookbook::InstallProvider < Chef::Provider::LWRPBase
  include ElasticsearchCookbook::Helpers
  include Chef::DSL::IncludeRecipe
  provides :elasticsearch_install

  def whyrun_supported?
    true # we only use core Chef resources that also support whyrun
  end

  def action_install
    if new_resource.type == 'tarball'
      install_tarball_wrapper_action
    elsif new_resource.type == 'package'
      install_package_wrapper_action
    elsif new_resource.type == 'repository'
      install_repo_wrapper_action
    else
      raise "#{install_type} is not a valid install type"
    end
  end

  def action_remove
    if new_resource.type == 'tarball'
      remove_tarball_wrapper_action
    elsif new_resource.type == 'package'
      remove_package_wrapper_action
    elsif new_resource.type == 'repository'
      remove_repo_wrapper_action
    else
      raise "#{install_type} is not a valid install type"
    end
  end

  protected

  def install_repo_wrapper_action
    es_user = find_es_resource(Chef.run_context, :elasticsearch_user, new_resource)
    unless es_user && es_user.username == 'elasticsearch' && es_user.groupname == 'elasticsearch'
      raise 'Custom usernames/group names is not supported in Elasticsearch 6+ repository installation'
    end

    if new_resource.enable_repository_actions
      if node['platform_family'] == 'debian'
        apt_r = apt_repo_resource
        apt_r.run_action(:add)
        new_resource.updated_by_last_action(true) if apt_r.updated_by_last_action?
      else
        yr_r = yum_repo_resource
        yr_r.run_action(:create)
        new_resource.updated_by_last_action(true) if yr_r.updated_by_last_action?
      end
    end

    if !new_resource.version.nil? && %w[rhel amazon].include?(node['platform_family']) && !new_resource.version.include?('-')
      # NB: yum repo packages are broken in Chef if you don't specify a release
      #     https://github.com/chef/chef/issues/4103
      new_resource.version = "#{new_resource.version}-1"
    end

    pkg_r = package 'elasticsearch' do
      options new_resource.package_options
      version new_resource.version
      action :nothing
    end

    pkg_r.run_action(:install)
    new_resource.updated_by_last_action(true) if pkg_r.updated_by_last_action?
  end

  def remove_repo_wrapper_action
    if new_resource.enable_repository_actions
      if node['platform_family'] == 'debian'
        apt_r = apt_repo_resource
        apt_r.run_action(:remove)
        new_resource.updated_by_last_action(true) if apt_r.updated_by_last_action?
      else
        yr_r = yum_repo_resource
        yr_r.run_action(:delete)
        new_resource.updated_by_last_action(true) if yr_r.updated_by_last_action?
      end
    end

    pkg_r = package 'elasticsearch' do
      options new_resource.package_options
      version new_resource.version
      action :nothing
    end
    pkg_r.run_action(:remove)
    new_resource.updated_by_last_action(true) if pkg_r.updated_by_last_action?
  end

  def install_package_wrapper_action
    es_user = find_es_resource(Chef.run_context, :elasticsearch_user, new_resource)
    unless es_user && es_user.username == 'elasticsearch' && es_user.groupname == 'elasticsearch'
      raise 'Custom usernames/group names is not supported in Elasticsearch 6+ package installation'
    end

    found_download_url = determine_download_url(new_resource, node)
    unless found_download_url
      raise 'Could not determine download url for package on this platform'
    end

    filename = found_download_url.split('/').last
    checksum = determine_download_checksum(new_resource, node)
    package_options = new_resource.package_options

    unless checksum
      Chef::Log.warn("No checksum was provided for #{found_download_url}, this may download a new package on every chef run!")
    end

    remote_file_r = remote_file "#{Chef::Config[:file_cache_path]}/#{filename}" do
      source found_download_url
      checksum checksum
      mode '0644'
      action :nothing
    end
    remote_file_r.run_action(:create)
    new_resource.updated_by_last_action(true) if remote_file_r.updated_by_last_action?

    pkg_r = if node['platform_family'] == 'debian'
              dpkg_package "#{Chef::Config[:file_cache_path]}/#{filename}" do
                options package_options
                action :nothing
              end
            else
              package "#{Chef::Config[:file_cache_path]}/#{filename}" do
                options package_options
                action :nothing
              end
            end

    pkg_r.run_action(:install)
    new_resource.updated_by_last_action(true) if pkg_r.updated_by_last_action?
  end

  def remove_package_wrapper_action
    package_url = determine_download_url(new_resource, node)
    filename = package_url.split('/').last

    pkg_r = if node['platform_family'] == 'debian'
              dpkg_package "#{Chef::Config[:file_cache_path]}/#{filename}" do
                action :nothing
              end
            else
              package "#{Chef::Config[:file_cache_path]}/#{filename}" do
                action :nothing
              end
            end

    pkg_r.run_action(:remove)
    new_resource.updated_by_last_action(true) if pkg_r.updated_by_last_action?
  end

  def install_tarball_wrapper_action
    include_recipe 'ark'

    es_user = find_es_resource(Chef.run_context, :elasticsearch_user, new_resource)
    found_download_url = determine_download_url(new_resource, node)
    unless found_download_url
      raise 'Could not determine download url for tarball on this platform'
    end

    ark_r = ark 'elasticsearch' do
      url   found_download_url
      owner es_user.username
      group es_user.groupname
      version new_resource.version
      has_binaries ['bin/elasticsearch', 'bin/elasticsearch-plugin']
      checksum determine_download_checksum(new_resource, node)
      prefix_root   new_resource.dir
      prefix_home   new_resource.dir

      not_if do
        link   = "#{new_resource.dir}/elasticsearch"
        target = "#{new_resource.dir}/elasticsearch-#{new_resource.version}"
        binary = "#{target}/bin/elasticsearch"

        ::File.directory?(link) && ::File.symlink?(link) && ::File.readlink(link) == target && ::File.exist?(binary)
      end
      action :nothing
    end
    ark_r.run_action(:install)
    new_resource.updated_by_last_action(true) if ark_r.updated_by_last_action?

    # destroy the sample config directory for tarball installs, or it will
    # take precedence beyond the default stuff in /etc/elasticsearch and within
    # /etc/sysconfig or /etc/default
    sample_r = directory "#{new_resource.dir}/elasticsearch/config" do
      action :nothing
      recursive true
    end
    sample_r.run_action(:delete)
    new_resource.updated_by_last_action(true) if sample_r.updated_by_last_action?
  end

  def remove_tarball_wrapper_action
    # remove the symlink to this version
    link_r = link "#{new_resource.dir}/elasticsearch" do
      only_if do
        link   = "#{new_resource.dir}/elasticsearch"
        target = "#{new_resource.dir}/elasticsearch-#{new_resource.version}"

        ::File.directory?(link) && ::File.symlink?(link) && ::File.readlink(link) == target
      end
      action :nothing
    end
    link_r.run_action(:delete)
    new_resource.updated_by_last_action(true) if link_r.updated_by_last_action?

    # remove the specific version
    d_r = directory "#{new_resource.dir}/elasticsearch-#{new_resource.version}" do
      recursive true
      action :nothing
    end
    d_r.run_action(:delete)
    new_resource.updated_by_last_action(true) if d_r.updated_by_last_action?
  end

  def yum_repo_resource
    yum_repository "elastic-#{new_resource.version.to_i}.x" do
      baseurl "https://artifacts.elastic.co/packages/#{new_resource.version.to_i}.x/yum"
      gpgkey 'https://artifacts.elastic.co/GPG-KEY-elasticsearch'
      action :nothing # :add, remove
    end
  end

  def apt_repo_resource
    apt_repository "elastic-#{new_resource.version.to_i}.x" do
      uri "https://artifacts.elastic.co/packages/#{new_resource.version.to_i}.x/apt"
      key 'https://artifacts.elastic.co/GPG-KEY-elasticsearch'
      components ['main']
      distribution 'stable'
      action :nothing # :create, :delete
    end
  end
end
