diff --git a/.kitchen.yml b/.kitchen.yml index 8abe9ea9e..50bb6ffb7 100644 --- a/.kitchen.yml +++ b/.kitchen.yml @@ -42,19 +42,26 @@ suites: require_chef_omnibus: 12.5.1 attributes: elasticsearch: - install_type: 'package' + install_type: package run_list: - recipe[java] - recipe[elasticsearch_test::default_with_plugins] - - name: elasticsearch_test # the override-everything use case + - name: override_default # the override-everything use case driver_config: require_chef_omnibus: 12.5.1 run_list: - recipe[java] - recipe[elasticsearch_test::default] + - name: override_package # the override-everything use case + driver_config: + require_chef_omnibus: 12.5.1 + run_list: + - recipe[java] + - recipe[elasticsearch_test::package] + - name: chef-12.4.3 driver_config: require_chef_omnibus: 12.4.3 @@ -90,12 +97,3 @@ suites: run_list: - recipe[java] - recipe[elasticsearch_test::default_with_plugins] - - - name: chef-11.18.12 - driver_config: - require_chef_omnibus: 11.18.12 - includes: - - ubuntu-14.04 - run_list: - - recipe[java] - - recipe[elasticsearch_test::default_with_plugins] diff --git a/Gemfile b/Gemfile index d30152b6c..4322fcad2 100644 --- a/Gemfile +++ b/Gemfile @@ -6,7 +6,7 @@ group :lint do end group :unit do - gem 'berkshelf', '~> 3' + gem 'berkshelf', '~> 4.0.0' gem 'chefspec', '>= 4.2' gem 'chef-sugar' end diff --git a/README.md b/README.md index 332ee388f..8b54ee16d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Elasticsearch Chef Cookbook This cookbook has been converted into a library cookbook as of version 1.0.0, -and supports Chef 12.4.1, 12.3.0, 12.2.1, and higher. It implements support for +and supports Chef 12.5.1, 12.4.3, 12.3.0, 12.2.1, 12.1.2, and higher. It implements support for CI as well as more modern testing with chefspec and test-kitchen. It no longer supports some of the more extraneous features such as discovery (use [chef search](http://docs.chef.io/chef_search.html) in your wrapper cookbook) or EBS device creation (use [the aws cookbook](https://github.com/opscode-cookbooks/aws)). @@ -9,19 +9,36 @@ device creation (use [the aws cookbook](https://github.com/opscode-cookbooks/aws The previous version of this cookbook may be found in the [0.3.x branch](https://github.com/elastic/cookbook-elasticsearch/tree/0.3.x). ## Default version, download URLs, and checksums -Please consult [attributes/default.rb](attributes/default.rb) for these values. -Both the recipes and resources/providers here source their default values for -Elasticsearch version, download URL, and Checksum from `attributes/default.rb`. + + + +## Attributes + +Please consult [attributes/default.rb](attributes/default.rb) for a large list +of checksums for many different archives and package files of different +elasticsearch versions. Both recipes and resources/providers here use those +default values. Please take note that you may use `%s` in your URL and this cookbook will use sprintf/format to insert the version parameter as a string into your download_url. -You may adjust the node attributes to force this cookbook to use different -default values for all three settings. +|Name|Default|Other values| +|----|-------|------------| +|`default['elasticsearch']['version']`|`'1.7.3'`|[See list](attributes/default.rb).| +|`default['elasticsearch']['install_type']`|`:tarball`|`:package`| +|`default['elasticsearch']['download_urls']['debian']`|`'https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-%s.deb'`|`%s` will be replaced with the version attribute above| +|`default['elasticsearch']['download_urls']['rhel']`|`'https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-%s.noarch.rpm'`|`%s` will be replaced with the version attribute above| +|`default['elasticsearch']['download_urls']['tar']`|`'https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-%s.tar.gz'`|`%s` will be replaced with the version attribute above| ## Recipes +Resources are the intended way to consume this cookbook, however we have +provided a single recipe that configures Elasticsearch by downloading an archive +containing a distribution of Elasticsearch, and extracting that into /usr/local. + +See the attributes section above to for what defaults you can adjust. + ### default The default recipe creates an elasticsearch user and group with the default @@ -29,6 +46,14 @@ options. ## Resources +You should be aware that potentially all resources in this cookbook look each +other up in the resource collection. By default, they will look for appropriate +resources named 'default' or 'elasticsearch', as in the examples below. For +example, if you use `elasticsearch_install 'default'`, `elasticsearch_config` +will use the first resource to determine how you installed Elasticsearch. If you +need to override this behavior, all resources accept `instance_name` as an +additional parameter, to be used for matching. + ### elasticsearch_user Actions: `:create`, `:remove` @@ -58,9 +83,9 @@ end Actions: `:install`, `:remove` Downloads the elasticsearch software, and unpacks it on the system. There are -currently two ways to install -- `package`, which downloads the appropriate +currently two ways to install -- `:package`, which downloads the appropriate package from elasticsearch.org and uses the package manager to install it, and -`tarball` which downloads a tarball from elasticsearch.org and unpacks it in +`:tarball` which downloads a tarball from elasticsearch.org and unpacks it in /usr/local on the system. The resource name is not used for anything in particular. This resource also comes with a `:remove` action which will remove the package or directory elasticsearch was unpacked into. @@ -68,9 +93,9 @@ the package or directory elasticsearch was unpacked into. You may always specify a download_url and/or download_checksum, and you may include `%s` which will be replaced by the version parameter you supply. -Please be sure to consult the above section 'Default version, download URLs, -and checksums' as that controls how Elasticsearch version, download URL and -checksum are determined if you omit them. +Please be sure to consult the above attribute section as that controls how +Elasticsearch version, download URL and checksum are determined if you omit +them. Examples: @@ -83,9 +108,6 @@ elasticsearch_install 'my_es_installation' do type :tarball # type of install dir '/usr/local' # where to install - owner 'elasticsearch' # user and group to install under - group 'elasticsearch' - download_url "https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-1.7.2.tar.gz" # sha256 download_checksum "6f81935e270c403681e120ec4395c28b2ddc87e659ff7784608b86beb5223dd2" @@ -108,6 +130,7 @@ elasticsearch_install 'my_es_installation' do download_url "https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-1.7.2.deb" # sha256 download_checksum "791fb9f2131be2cf8c1f86ca35e0b912d7155a53f89c2df67467ca2105e77ec2" + instance_name 'elasticsearch' action :install # could be :remove as well end ``` @@ -151,12 +174,13 @@ More complicated - elasticsearch_configure 'my_elasticsearch' do # if you override one of these, you probably want to override all dir '/usr/local/awesome' - path_conf "/usr/local/awesome/etc/elasticsearch" - path_data "/usr/local/awesome/var/data/elasticsearch" - path_logs "/usr/local/awesome/var/log/elasticsearch" + path_conf tarball: "/usr/local/awesome/etc/elasticsearch" + path_data tarball: "/usr/local/awesome/var/data/elasticsearch" + path_logs tarball: "/usr/local/awesome/var/log/elasticsearch" + path_pid tarball: "/usr/local/awesome/var/run/elasticsearch" + path_plugins tarball: "/usr/local/elasticsearch/plugins" + path_bin tarball: "/usr/local/bin" - user 'foo' - group 'bar' logging({:"action" => 'INFO'}) allocated_memory '123m' @@ -194,10 +218,6 @@ elasticsearch_service 'elasticsearch' ``` elasticsearch_service 'elasticsearch-crazy' do node_name 'crazy' - path_conf '/usr/local/awesome/etc/elasticsearch' - pid_path '/usr/local/awesome/var/run' - user 'foo' - group 'bar' end ``` @@ -224,9 +244,7 @@ To run multiple instances per machine, an explicit `plugin_dir` location has to be provided: ``` -elasticsearch_plugin 'mobz/elasticsearch-head' do - plugin_dir '/usr/local/awesome/elasticsearch-1.7.2/plugins' -end +elasticsearch_plugin 'mobz/elasticsearch-head' ``` NB: You [may encounter issues on certain distros](http://blog.backslasher.net/java-ssl-crash.html) with NSS 3.16.1 and OpenJDK 7.x. diff --git a/attributes/default.rb b/attributes/default.rb index 03ae0a3c6..f73638782 100644 --- a/attributes/default.rb +++ b/attributes/default.rb @@ -1,6 +1,6 @@ # elasticsearch version & install type default['elasticsearch']['version'] = '1.7.3' -default['elasticsearch']['install_type'] = 'tarball' +default['elasticsearch']['install_type'] = :tarball # platform_family keyed download URLs default['elasticsearch']['download_urls']['debian'] = 'https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-%s.deb' diff --git a/libraries/helpers.rb b/libraries/helpers.rb index af574e7c3..8f8b35be1 100644 --- a/libraries/helpers.rb +++ b/libraries/helpers.rb @@ -1,6 +1,67 @@ module ElasticsearchCookbook # Helper methods included by various providers and passed to the template engine module Helpers + + def find_es_resource(run_context, resource_type, resource) + resource_name = resource.name + instance_name = resource.instance_name + + # if we are truly given a specific name to find + name_match = find_exact_resource(run_context, resource_type, resource_name) rescue nil + return name_match if name_match + + # first try by instance name attribute + name_instance = find_instance_name_resource(run_context, resource_type, instance_name) rescue nil + return name_instance if name_instance + + # otherwise try the defaults + name_default = find_exact_resource(run_context, resource_type, 'default') rescue nil + name_elasticsearch = find_exact_resource(run_context, resource_type, 'elasticsearch') rescue nil + + # if we found exactly one default name that matched + return name_default if name_default && !name_elasticsearch + return name_elasticsearch if name_elasticsearch && !name_default + + raise "Could not find exactly one #{resource_type} resource, and no specific resource or instance name was given" + end + + # find exactly the resource name and type, but raise if there's multiple matches + # see https://github.com/chef/chef/blob/master/lib/chef/resource_collection/resource_set.rb#L80 + def find_exact_resource(run_context, resource_type, resource_name) + rc = run_context.resource_collection + result = rc.find(resource_type => resource_name) + + if result && result.kind_of?(Array) + str = '' + str << "more than one #{resource_type} was found, " + str << "you must specify a precise resource name" + raise str + end + + return result + end + + def find_instance_name_resource(run_context, resource_type, instance_name) + results = [] + rc = run_context.resource_collection + + rc.each do |r| + next unless r.resource_name == resource_type && r.instance_name == instance_name + results << r + end + + if !results.empty? && results.length > 1 + str = '' + str << "more than one #{resource_type} was found, " + str << "you must specify a precise instance name" + raise str + elsif !results.empty? + return results.first + end + + return nil # falsey + end + def determine_version(new_resource, node) if new_resource.version new_resource.version.to_s @@ -56,14 +117,6 @@ def determine_download_checksum(new_resource, node) end end - def get_tarball_home_dir(new_resource, node) - new_resource.dir || node['ark']['prefix_home'] - end - - def get_tarball_root_dir(new_resource, node) - new_resource.dir || node['ark']['prefix_root'] - end - # This method takes a hash, but will convert to mash def print_value(data, key, options = {}) separator = options[:separator] || ': ' diff --git a/libraries/provider_configure.rb b/libraries/provider_configure.rb index c111a3d6f..8e2242130 100644 --- a/libraries/provider_configure.rb +++ b/libraries/provider_configure.rb @@ -1,129 +1,125 @@ -class Chef - # Chef Provider for configuring an elasticsearch instance - class Provider::ElasticsearchConfigure < Chef::Provider::LWRPBase - include ElasticsearchCookbook::Helpers +# Chef Provider for configuring an elasticsearch instance +class ElasticsearchCookbook::ConfigureProvider < Chef::Provider::LWRPBase + include ElasticsearchCookbook::Helpers - action :manage do - converge_by('configure elasticsearch instance') do - # if a subdir parameter is missing but dir is set, infer the subdir name - # then go and be sure it's also set in the YML hash if it wasn't given there - if new_resource.dir && !new_resource.path_conf - new_resource.path_conf "#{new_resource.dir}/etc/elasticsearch" - end - if new_resource.path_conf && new_resource.default_configuration['path.conf'].nil? - new_resource.default_configuration['path.conf'] = new_resource.path_conf - end + provides :elasticsearch_configure - if new_resource.dir && !new_resource.path_data - new_resource.path_data "#{new_resource.dir}/var/data/elasticsearch" - end - if new_resource.path_data && new_resource.default_configuration['path.data'].nil? - new_resource.default_configuration['path.data'] = new_resource.path_data - end + action :manage do + converge_by('configure elasticsearch instance') do + es_install = find_es_resource(run_context, :elasticsearch_install, new_resource) - if new_resource.dir && !new_resource.path_logs - new_resource.path_logs "#{new_resource.dir}/var/log/elasticsearch" - end - if new_resource.path_logs && new_resource.default_configuration['path.logs'].nil? - new_resource.default_configuration['path.logs'] = new_resource.path_logs - end + # if a subdir parameter is missing but dir is set, infer the subdir name + # then go and be sure it's also set in the YML hash if it wasn't given there + if new_resource.path_conf[es_install.type] && new_resource.default_configuration['path.conf'].nil? + new_resource.default_configuration['path.conf'] = new_resource.path_conf[es_install.type] + end - # calculation for memory allocation; 50% or 31g, whatever is smaller - unless new_resource.allocated_memory - half = ((node['memory']['total'].to_i * 0.5).floor / 1024) - malloc_str = (half > 31_000 ? '31g' : "#{half}m") - new_resource.allocated_memory malloc_str - end + if new_resource.path_data[es_install.type] && new_resource.default_configuration['path.data'].nil? + new_resource.default_configuration['path.data'] = new_resource.path_data[es_install.type] + end - # Create ES directories - # - [new_resource.path_conf, new_resource.path_logs].each do |path| - d = directory path do - owner new_resource.user - group new_resource.group - mode 0755 - recursive true - action :nothing - end - d.run_action(:create) - new_resource.updated_by_last_action(true) if d.updated_by_last_action? - end + if new_resource.path_logs[es_install.type] && new_resource.default_configuration['path.logs'].nil? + new_resource.default_configuration['path.logs'] = new_resource.path_logs[es_install.type] + end - # Create data path directories - # - data_paths = new_resource.path_data.is_a?(Array) ? new_resource.path_data : new_resource.path_data.split(',') + # calculation for memory allocation; 50% or 31g, whatever is smaller + unless new_resource.allocated_memory + half = ((node['memory']['total'].to_i * 0.5).floor / 1024) + malloc_str = (half > 31_000 ? '31g' : "#{half}m") + new_resource.allocated_memory malloc_str + end - data_paths.each do |path| - d = directory path.strip do - owner new_resource.user - group new_resource.group - mode 0755 - recursive true - action :nothing - end - d.run_action(:create) - new_resource.updated_by_last_action(true) if d.updated_by_last_action? - end + # lookup existing user resource + es_user = find_es_resource(run_context, :elasticsearch_user, new_resource) - shell_template = template 'elasticsearch.in.sh' do - path "#{new_resource.path_conf}/elasticsearch.in.sh" - source new_resource.template_elasticsearch_env - cookbook 'elasticsearch' - owner new_resource.user - group new_resource.group + # Create ES directories + # + [new_resource.path_conf[es_install.type], new_resource.path_logs[es_install.type]].each do |path| + d = directory path do + owner es_user.username + group es_user.groupname mode 0755 - variables(java_home: new_resource.java_home, - es_home: new_resource.es_home, - es_config: new_resource.path_conf, - allocated_memory: new_resource.allocated_memory, - Xms: new_resource.allocated_memory, - Xmx: new_resource.allocated_memory, - Xss: new_resource.thread_stack_size, - gc_settings: new_resource.gc_settings, - env_options: new_resource.env_options) + recursive true action :nothing end - shell_template.run_action(:create) - new_resource.updated_by_last_action(true) if shell_template.updated_by_last_action? + d.run_action(:create) + new_resource.updated_by_last_action(true) if d.updated_by_last_action? + end + + # Create data path directories + # + data_paths = new_resource.path_data[es_install.type].is_a?(Array) ? new_resource.path_data[es_install.type] : new_resource.path_data[es_install.type].split(',') - # Create ES logging file - # - logging_template = template 'logging.yml' do - path "#{new_resource.path_conf}/logging.yml" - source new_resource.template_logging_yml - cookbook 'elasticsearch' - owner new_resource.user - group new_resource.group + data_paths.each do |path| + d = directory path.strip do + owner es_user.username + group es_user.groupname mode 0755 - variables(logging: new_resource.logging) + recursive true action :nothing end - logging_template.run_action(:create) - new_resource.updated_by_last_action(true) if logging_template.updated_by_last_action? + d.run_action(:create) + new_resource.updated_by_last_action(true) if d.updated_by_last_action? + end - merged_configuration = new_resource.default_configuration.merge(new_resource.configuration) - merged_configuration['#_seen'] = {} # magic state variable for what we've seen in a config + shell_template = template 'elasticsearch.in.sh' do + path "#{new_resource.path_conf[es_install.type]}/elasticsearch.in.sh" + source new_resource.template_elasticsearch_env + cookbook 'elasticsearch' + owner es_user.username + group es_user.groupname + mode 0755 + variables(java_home: new_resource.java_home, + es_home: es_user.homedir, + es_config: new_resource.path_conf[es_install.type], + allocated_memory: new_resource.allocated_memory, + Xms: new_resource.allocated_memory, + Xmx: new_resource.allocated_memory, + Xss: new_resource.thread_stack_size, + gc_settings: new_resource.gc_settings, + env_options: new_resource.env_options) + action :nothing + end + shell_template.run_action(:create) + new_resource.updated_by_last_action(true) if shell_template.updated_by_last_action? - # warn if someone is using symbols. we don't support. - found_symbols = merged_configuration.keys.select { |s| s.is_a?(Symbol) } - unless found_symbols.empty? - Chef::Log.warn("Please change the following to strings in order to work with this Elasticsearch cookbook: #{found_symbols.join(',')}") - end + # Create ES logging file + # + logging_template = template 'logging.yml' do + path "#{new_resource.path_conf[es_install.type]}/logging.yml" + source new_resource.template_logging_yml + cookbook 'elasticsearch' + owner es_user.username + group es_user.groupname + mode 0755 + variables(logging: new_resource.logging) + action :nothing + end + logging_template.run_action(:create) + new_resource.updated_by_last_action(true) if logging_template.updated_by_last_action? - yml_template = template 'elasticsearch.yml' do - path "#{new_resource.path_conf}/elasticsearch.yml" - source new_resource.template_elasticsearch_yml - cookbook 'elasticsearch' - owner new_resource.user - group new_resource.group - mode 0755 - helpers(ElasticsearchCookbook::Helpers) - variables(config: merged_configuration) - action :nothing - end - yml_template.run_action(:create) - new_resource.updated_by_last_action(true) if yml_template.updated_by_last_action? + merged_configuration = new_resource.default_configuration.merge(new_resource.configuration) + merged_configuration['#_seen'] = {} # magic state variable for what we've seen in a config + + # warn if someone is using symbols. we don't support. + found_symbols = merged_configuration.keys.select { |s| s.is_a?(Symbol) } + unless found_symbols.empty? + Chef::Log.warn("Please change the following to strings in order to work with this Elasticsearch cookbook: #{found_symbols.join(',')}") + end + + yml_template = template 'elasticsearch.yml' do + path "#{new_resource.path_conf[es_install.type]}/elasticsearch.yml" + source new_resource.template_elasticsearch_yml + cookbook 'elasticsearch' + owner es_user.username + group es_user.groupname + mode 0755 + helpers(ElasticsearchCookbook::Helpers) + variables(config: merged_configuration) + action :nothing end + yml_template.run_action(:create) + new_resource.updated_by_last_action(true) if yml_template.updated_by_last_action? end end end diff --git a/libraries/provider_install.rb b/libraries/provider_install.rb index 0286cea33..c36913482 100644 --- a/libraries/provider_install.rb +++ b/libraries/provider_install.rb @@ -1,138 +1,139 @@ -class Chef - # Chef Provider for installing or removing Elasticsearch from package or tarball - # downloaded from elasticsearch.org and installed by package manager or ark resource - class Provider::ElasticsearchInstall < Chef::Provider::LWRPBase - include ElasticsearchCookbook::Helpers - include Chef::DSL::IncludeRecipe - provides :elasticsearch_install if respond_to?(:provides) - - action :install do - install_type = determine_install_type(new_resource, node) - converge_by("#{new_resource.name} - install #{install_type}") do - if install_type == 'tarball' || install_type == 'tar' - install_tarball_wrapper_action - elsif install_type == 'package' - install_package_wrapper_action - else - fail "#{install_type} is not a valid install type" - end +# 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 + + action :install do + install_type = determine_install_type(new_resource, node) + converge_by("#{new_resource.name} - install #{install_type}") do + if install_type == 'tarball' || install_type == 'tar' + install_tarball_wrapper_action + elsif install_type == 'package' + install_package_wrapper_action + else + fail "#{install_type} is not a valid install type" end end + end - action :remove do - install_type = determine_install_type(new_resource, node) - converge_by("#{new_resource.name} - remove #{install_type}") do - if install_type == 'tarball' || install_type == 'tar' - remove_tarball_wrapper_action - elsif install_type == 'package' - remove_package_wrapper_action - else - fail "#{install_type} is not a valid install type" - end + action :remove do + install_type = determine_install_type(new_resource, node) + converge_by("#{new_resource.name} - remove #{install_type}") do + if install_type == 'tarball' || install_type == 'tar' + remove_tarball_wrapper_action + elsif install_type == 'package' + remove_package_wrapper_action + else + fail "#{install_type} is not a valid install type" end end + end + + protected - protected + def install_package_wrapper_action + download_url = determine_download_url(new_resource, node) + filename = download_url.split('/').last + checksum = determine_download_checksum(new_resource, node) + package_options = new_resource.package_options - def install_package_wrapper_action - download_url = determine_download_url(new_resource, node) - filename = download_url.split('/').last - checksum = determine_download_checksum(new_resource, node) - package_options = new_resource.package_options + remote_file_r = remote_file "#{Chef::Config[:file_cache_path]}/#{filename}" do + source download_url + checksum checksum + mode 00644 + action :nothing + end + remote_file_r.run_action(:create) + new_resource.updated_by_last_action(true) if remote_file_r.updated_by_last_action? - remote_file_r = remote_file "#{Chef::Config[:file_cache_path]}/#{filename}" do - source download_url - checksum checksum - mode 00644 + if node['platform_family'] == 'debian' + pkg_r = dpkg_package "#{Chef::Config[:file_cache_path]}/#{filename}" do + options package_options action :nothing end - remote_file_r.run_action(:create) - new_resource.updated_by_last_action(true) if remote_file_r.updated_by_last_action? - - if node['platform_family'] == 'debian' - pkg_r = dpkg_package "#{Chef::Config[:file_cache_path]}/#{filename}" do - options package_options - action :nothing - end - pkg_r.run_action(:install) - new_resource.updated_by_last_action(true) if pkg_r.updated_by_last_action? - else - pkg_r = package "#{Chef::Config[:file_cache_path]}/#{filename}" do - options package_options - action :nothing - end - pkg_r.run_action(:install) - new_resource.updated_by_last_action(true) if pkg_r.updated_by_last_action? + pkg_r.run_action(:install) + new_resource.updated_by_last_action(true) if pkg_r.updated_by_last_action? + else + pkg_r = package "#{Chef::Config[:file_cache_path]}/#{filename}" do + options package_options + action :nothing end + pkg_r.run_action(:install) + new_resource.updated_by_last_action(true) if pkg_r.updated_by_last_action? end + end - def remove_package_wrapper_action - package_url = get_package_url(new_resource, node) - filename = package_url.split('/').last + def remove_package_wrapper_action + package_url = get_package_url(new_resource, node) + filename = package_url.split('/').last - if node['platform_family'] == 'debian' - pkg_r = dpkg_package "#{Chef::Config[:file_cache_path]}/#{filename}" do - action :nothing - end - pkg_r.run_action(:remove) - new_resource.updated_by_last_action(true) if pkg_r.updated_by_last_action? - else - pkg_r = package "#{Chef::Config[:file_cache_path]}/#{filename}" do - action :nothing - end - pkg_r.run_action(:remove) - new_resource.updated_by_last_action(true) if pkg_r.updated_by_last_action? + if node['platform_family'] == 'debian' + pkg_r = dpkg_package "#{Chef::Config[:file_cache_path]}/#{filename}" do + action :nothing end - end - - def install_tarball_wrapper_action - include_recipe 'ark' - include_recipe 'curl' - - ark_r = ark 'elasticsearch' do - url determine_download_url(new_resource, node) - owner new_resource.owner - group new_resource.group - version determine_version(new_resource, node) - has_binaries ['bin/elasticsearch', 'bin/plugin'] - checksum determine_download_checksum(new_resource, node) - prefix_root get_tarball_root_dir(new_resource, node) - prefix_home get_tarball_home_dir(new_resource, node) - - not_if do - link = "#{new_resource.dir}/elasticsearch" - target = "#{new_resource.dir}/elasticsearch-#{determine_version(new_resource, node)}" - binary = "#{target}/bin/elasticsearch" - - ::File.directory?(link) && ::File.symlink?(link) && ::File.readlink(link) == target && ::File.exist?(binary) - end + pkg_r.run_action(:remove) + new_resource.updated_by_last_action(true) if pkg_r.updated_by_last_action? + else + pkg_r = package "#{Chef::Config[:file_cache_path]}/#{filename}" do action :nothing end - ark_r.run_action(:install) - new_resource.updated_by_last_action(true) if ark_r.updated_by_last_action? + pkg_r.run_action(:remove) + new_resource.updated_by_last_action(true) if pkg_r.updated_by_last_action? end + 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-#{determine_version(new_resource, node)}" + def install_tarball_wrapper_action + include_recipe 'ark' + include_recipe 'curl' - ::File.directory?(link) && ::File.symlink?(link) && ::File.readlink(link) == target - end - action :nothing + es_user = find_es_resource(run_context, :elasticsearch_user, new_resource) + + ark_r = ark 'elasticsearch' do + url determine_download_url(new_resource, node) + owner es_user.username + group es_user.groupname + version determine_version(new_resource, node) + has_binaries ['bin/elasticsearch', 'bin/plugin'] + checksum determine_download_checksum(new_resource, node) + prefix_root new_resource.dir[new_resource.type] + prefix_home new_resource.dir[new_resource.type] + + not_if do + link = "#{new_resource.dir}/elasticsearch" + target = "#{new_resource.dir}/elasticsearch-#{determine_version(new_resource, node)}" + binary = "#{target}/bin/elasticsearch" + + ::File.directory?(link) && ::File.symlink?(link) && ::File.readlink(link) == target && ::File.exist?(binary) end - link_r.run_action(:delete) - new_resource.updated_by_last_action(true) if link_r.updated_by_last_action? + action :nothing + end + #ark_p = Chef::ProviderResolver.new(node, ark_r, :install).resolve + ark_r.run_action(:install) + new_resource.updated_by_last_action(true) if ark_r.updated_by_last_action? + end - # remove the specific version - d_r = directory "#{new_resource.dir}/elasticsearch-#{determine_version(new_resource, node)}" do - action :nothing + 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-#{determine_version(new_resource, node)}" + + ::File.directory?(link) && ::File.symlink?(link) && ::File.readlink(link) == target end - d_r.run_action(:delete) - new_resource.updated_by_last_action(true) if d_r.updated_by_last_action? + 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-#{determine_version(new_resource, node)}" do + action :nothing end + d_r.run_action(:delete) + new_resource.updated_by_last_action(true) if d_r.updated_by_last_action? end end diff --git a/libraries/provider_plugin.rb b/libraries/provider_plugin.rb index e21851d9e..e63b399de 100644 --- a/libraries/provider_plugin.rb +++ b/libraries/provider_plugin.rb @@ -1,54 +1,72 @@ +# Chef Provider for installing an elasticsearch plugin +class ElasticsearchCookbook::PluginProvider < Chef::Provider::LWRPBase + include ElasticsearchCookbook::Helpers + include Chef::Mixin::ShellOut -class Chef - # Chef Provider for installing an elasticsearch plugin - class Provider::ElasticsearchPlugin < Chef::Provider::LWRPBase - include ElasticsearchCookbook::Helpers - include Chef::Mixin::ShellOut - - action :install do - name = new_resource.plugin_name - version = new_resource.version ? "/#{new_resource.version}" : nil - url = new_resource.url ? " -url #{new_resource.url}" : nil - - fail 'Could not determine the plugin directory. Please set plugin_dir on this resource.' unless new_resource.plugin_dir - converge_by("install plugin #{name}") do - plugin_exists = begin - Dir.entries(new_resource.plugin_dir).any? do |plugin| - next if plugin =~ /^\./ - name.include? plugin - end - rescue - false + provides :elasticsearch_plugin + + action :install do + es_user = find_es_resource(run_context, :elasticsearch_user, new_resource) + es_install = find_es_resource(run_context, :elasticsearch_install, new_resource) + es_conf = find_es_resource(run_context, :elasticsearch_configure, new_resource) + + begin + if es_user.username != 'root' && es_install.version.to_f < 2 + Chef::Log.warn('Elasticsearch < 2.0.0 requires plugins be installed as root') + end + rescue + Chef::Log.warn("Could not parse #{es_install.version} as floating point number") + end + + name = new_resource.plugin_name + version = new_resource.version ? "/#{new_resource.version}" : nil + url = new_resource.url ? " -url #{new_resource.url}" : nil + + fail "Could not determine the plugin directory (#{es_conf.path_plugins[es_install.type]}). Please check elasticsearch_configure[#{es_conf.name}]." unless es_conf.path_plugins[es_install.type] # may not exist yet if first plugin + fail "Could not determine the binary directory (#{es_conf.path_bin[es_install.type]}). Please check elasticsearch_configure[#{es_conf.name}]." unless es_conf.path_bin[es_install.type] && ::File.exist?(es_conf.path_bin[es_install.type]) + converge_by("install plugin #{name}") do + plugin_exists = begin + Dir.entries(es_conf.path_plugins[es_install.type]).any? do |plugin| + next if plugin =~ /^\./ + name.include? plugin end + rescue + false + end - unless plugin_exists - # automatically raises on error, logs command output - shell_out!("#{new_resource.bindir}/plugin -install #{name}#{version}#{url}".split(' '), user: new_resource.user, group: new_resource.group) - new_resource.updated_by_last_action(true) - end + # shell_out! automatically raises on error, logs command output + unless plugin_exists + # required for package installs that show up with parent dir owned by root + shell_out!("mkdir -p #{es_conf.path_plugins[es_install.type]}") unless ::File.exist?(es_conf.path_plugins[es_install.type]) + shell_out!("chown #{es_user.username}:#{es_user.groupname} #{es_conf.path_plugins[es_install.type]}") + + # do the actual install + shell_out!("#{es_conf.path_bin[es_install.type]}/plugin install #{name}#{version}#{url}".split(' '), user: es_user.username, group: es_user.groupname) + + new_resource.updated_by_last_action(true) end - end # action - - action :remove do - name = new_resource.plugin_name - - fail 'Could not determine the plugin directory. Please set plugin_dir on this resource.' unless new_resource.plugin_dir - converge_by("install plugin #{name}") do - plugin_exists = begin - Dir.entries(new_resource.plugin_dir).any? do |plugin| - next if plugin =~ /^\./ - name.include? plugin - end - rescue - false + end + end # action + + action :remove do + name = new_resource.plugin_name + + fail 'Could not determine the plugin directory. Please set plugin_dir on this resource.' unless new_resource.plugin_dir[es_install.type] + converge_by("remove plugin #{name}") do + plugin_exists = begin + Dir.entries(new_resource.plugin_dir).any? do |plugin| + next if plugin =~ /^\./ + name.include? plugin end + rescue + false + end - if plugin_exists - # automatically raises on error, logs command output - shell_out!("#{new_resource.bindir}/plugin -remove #{name}".split(' '), user: new_resource.user, group: new_resource.group) - new_resource.updated_by_last_action(true) - end + if plugin_exists + # automatically raises on error, logs command output + shell_out!("#{new_resource.path_bin[es_install.type]}/plugin -remove #{name}".split(' '), user: es_user.username, group: es_user.groupname) + new_resource.updated_by_last_action(true) end - end # action - end # provider -end # chef class + end + end # action +end # provider diff --git a/libraries/provider_service.rb b/libraries/provider_service.rb index c335dda2c..508be531a 100644 --- a/libraries/provider_service.rb +++ b/libraries/provider_service.rb @@ -1,80 +1,85 @@ -class Chef - # Chef Provider for configuring an elasticsearch service in the init system - class Provider::ElasticsearchService < Chef::Provider::LWRPBase - action :remove do - fail "#{new_resource} remove not currently implemented" - end +# Chef Provider for configuring an elasticsearch service in the init system +class ElasticsearchCookbook::ServiceProvider < Chef::Provider::LWRPBase + + provides :elasticsearch_service + include ElasticsearchCookbook::Helpers + + action :remove do + fail "#{new_resource} remove not currently implemented" + end + + action :configure do + converge_by('configure elasticsearch service') do + es_user = find_es_resource(run_context, :elasticsearch_user, new_resource) + es_install = find_es_resource(run_context, :elasticsearch_install, new_resource) + es_conf = find_es_resource(run_context, :elasticsearch_configure, new_resource) - action :configure do - converge_by('configure elasticsearch service') do - # pick one if we have been given a path - unless new_resource.pid_file - new_resource.pid_file "#{new_resource.pid_path}/#{Chef::Config[:node_name].to_s.gsub(/\W/, '_')}.pid" - end + merged_configuration = es_conf.default_configuration.merge(es_conf.configuration) + sanitized_node_name = merged_configuration['node.name'].to_s.gsub(/\W/, '_') + pid_file = "#{es_conf.path_pid[es_install.type]}/#{sanitized_node_name}.pid" - d_r = directory new_resource.pid_path do - mode '0755' - recursive true - action :nothing - end - d_r.run_action(:create) - new_resource.updated_by_last_action(true) if d_r.updated_by_last_action? + d_r = directory es_conf.path_pid[es_install.type] do + mode '0755' + recursive true + action :nothing + end + d_r.run_action(:create) + new_resource.updated_by_last_action(true) if d_r.updated_by_last_action? - # Create service - # - init_r = template "/etc/init.d/#{new_resource.service_name}" do - source new_resource.init_source - cookbook new_resource.init_cookbook - owner 'root' - mode 0755 - variables(nofile_limit: new_resource.nofile_limit, - memlock_limit: new_resource.memlock_limit, - pid_file: new_resource.pid_file, - path_conf: new_resource.path_conf, - user: new_resource.user, - platform_family: node.platform_family, - bindir: new_resource.bindir, - http_port: 9200, # TODO: does the init script really need this? - node_name: new_resource.node_name, - service_name: new_resource.service_name, - args: new_resource.args) - action :nothing - end - init_r.run_action(:create) - new_resource.updated_by_last_action(true) if init_r.updated_by_last_action? + # Create service + # + init_r = template "/etc/init.d/#{new_resource.service_name}" do + source new_resource.init_source + cookbook new_resource.init_cookbook + owner 'root' + mode 0755 + variables(nofile_limit: new_resource.nofile_limit, + memlock_limit: new_resource.memlock_limit, + pid_file: pid_file, + path_conf: es_conf.path_conf[es_install.type], + user: es_user.username, + platform_family: node.platform_family, + bindir: es_conf.path_bin[es_install.type], + http_port: 9200, # TODO: does the init script really need this? + node_name: sanitized_node_name, + service_name: new_resource.service_name, + args: new_resource.args) + action :nothing + end + init_r.run_action(:create) + new_resource.updated_by_last_action(true) if init_r.updated_by_last_action? - # Increase open file and memory limits - # - bash_r = bash 'enable user limits' do - user 'root' + # Increase open file and memory limits + # + bash_r = bash 'enable user limits' do + user 'root' - code <<-END.gsub(/^ /, '') - echo 'session required pam_limits.so' >> /etc/pam.d/su - END + code <<-END.gsub(/^ /, '') + echo 'session required pam_limits.so' >> /etc/pam.d/su + END - not_if { ::File.read('/etc/pam.d/su').match(/^session required pam_limits\.so/) } - action :nothing - end - bash_r.run_action(:run) - new_resource.updated_by_last_action(true) if bash_r.updated_by_last_action? + not_if { ::File.read('/etc/pam.d/su').match(/^session required pam_limits\.so/) } + action :nothing + end + bash_r.run_action(:run) + new_resource.updated_by_last_action(true) if bash_r.updated_by_last_action? - secf_r = file '/etc/security/limits.d/10-elasticsearch.conf' do - content <<-END.gsub(/^ /, '') - #{new_resource.user} - nofile #{new_resource.nofile_limit} - #{new_resource.user} - memlock #{new_resource.memlock_limit} - END - end - secf_r.run_action(:create) - new_resource.updated_by_last_action(true) if secf_r.updated_by_last_action? + secf_r = file '/etc/security/limits.d/10-elasticsearch.conf' do + content <<-END.gsub(/^ /, '') + #{es_user.username} - nofile #{new_resource.nofile_limit} + #{es_user.username} - memlock #{new_resource.memlock_limit} + END + end + secf_r.run_action(:create) + new_resource.updated_by_last_action(true) if secf_r.updated_by_last_action? - svc_r = service new_resource.service_name do - supports :status => true, :restart => true - action :nothing - end - new_resource.service_actions.each do |act| - svc_r.run_action(act) - new_resource.updated_by_last_action(true) if svc_r.updated_by_last_action? - end + svc_r = service new_resource.service_name do + supports :status => true, :restart => true + action :nothing + end + new_resource.service_actions.each do |act| + svc_r.run_action(act) + new_resource.updated_by_last_action(true) if svc_r.updated_by_last_action? end end end diff --git a/libraries/provider_user.rb b/libraries/provider_user.rb index 4e3237311..a85ac3c2f 100644 --- a/libraries/provider_user.rb +++ b/libraries/provider_user.rb @@ -1,52 +1,53 @@ +# Chef Provider for creating a user and group for Elasticsearch +class ElasticsearchCookbook::UserProvider < Chef::Provider::LWRPBase + include ElasticsearchCookbook::Helpers -class Chef - # Chef Provider for creating a user and group for Elasticsearch - class Provider::ElasticsearchUser < Chef::Provider::LWRPBase - action :create do - converge_by("create elasticsearch_user resource #{new_resource.name}") do - unless new_resource.homedir - # if unset, default it to a calculated value - new_resource.homedir ::File.join(new_resource.homedir_parent, new_resource.homedir_name) - end + provides :elasticsearch_user - group_r = group new_resource.groupname do - gid new_resource.gid - action :nothing - system true - end - group_r.run_action(:create) - new_resource.updated_by_last_action(true) if group_r.updated_by_last_action? + action :create do + converge_by("create elasticsearch_user resource #{new_resource.name}") do + unless new_resource.homedir + # if unset, default it to a calculated value + new_resource.homedir ::File.join(new_resource.homedir_parent, new_resource.homedir_name) + end + + group_r = group new_resource.groupname do + gid new_resource.gid + action :nothing + system true + end + group_r.run_action(:create) + new_resource.updated_by_last_action(true) if group_r.updated_by_last_action? - user_r = user new_resource.username do - comment new_resource.comment - home new_resource.homedir - shell new_resource.shell - uid new_resource.uid - gid new_resource.groupname - supports manage_home: false - action :nothing - system true - end - user_r.run_action(:create) - new_resource.updated_by_last_action(true) if user_r.updated_by_last_action? + user_r = user new_resource.username do + comment new_resource.comment + home new_resource.homedir + shell new_resource.shell + uid new_resource.uid + gid new_resource.groupname + supports manage_home: false + action :nothing + system true end + user_r.run_action(:create) + new_resource.updated_by_last_action(true) if user_r.updated_by_last_action? end + end - action :remove do - converge_by("remove elasticsearch_user resource #{new_resource.name}") do - # delete user before deleting the group - user_r = user new_resource.username do - action :nothing - end - user_r.run_action(:remove) - new_resource.updated_by_last_action(true) if user_r.updated_by_last_action? + action :remove do + converge_by("remove elasticsearch_user resource #{new_resource.name}") do + # delete user before deleting the group + user_r = user new_resource.username do + action :nothing + end + user_r.run_action(:remove) + new_resource.updated_by_last_action(true) if user_r.updated_by_last_action? - group_r = group new_resource.groupname do - action :nothing - end - group_r.run_action(:remove) - new_resource.updated_by_last_action(true) if group_r.updated_by_last_action? + group_r = group new_resource.groupname do + action :nothing end + group_r.run_action(:remove) + new_resource.updated_by_last_action(true) if group_r.updated_by_last_action? end end end diff --git a/libraries/resource_configure.rb b/libraries/resource_configure.rb index 48265d840..7769b9467 100644 --- a/libraries/resource_configure.rb +++ b/libraries/resource_configure.rb @@ -1,74 +1,90 @@ +# Chef Resource for configuring an Elasticsearch node +class ElasticsearchCookbook::ConfigureResource < Chef::Resource::LWRPBase + resource_name :elasticsearch_configure + provides :elasticsearch_configure -class Chef - # Chef Resource for configuring an Elasticsearch node - # - class Resource::ElasticsearchConfigure < Chef::Resource::LWRPBase - resource_name :elasticsearch_configure if respond_to?(:resource_name) - - actions(:manage, :remove) - default_action :manage + actions(:manage, :remove) + default_action :manage - # if you override one of these, you should probably override them all - attribute(:dir, kind_of: String, default: '/usr/local') # creates /usr/local/elasticsearch - attribute(:path_conf, kind_of: String, default: nil) # default "/usr/local/etc/elasticsearch" - attribute(:path_data, kind_of: String, default: nil) # default "/usr/local/var/data/elasticsearch" - attribute(:path_logs, kind_of: String, default: nil) # default "/usr/local/var/log/elasticsearch" + # this is what helps the various resources find each other + attribute(:instance_name, kind_of: String, default: nil) - attribute(:user, kind_of: String, default: 'elasticsearch') - attribute(:group, kind_of: String, default: 'elasticsearch') + # if you override one of these, you should probably override them all + attribute(:path_conf, kind_of: Hash, default: { + package: '/etc/elasticsearch', + tarball: '/usr/local/etc/elasticsearch', + }) + attribute(:path_data, kind_of: Hash, default: { + package: '/var/lib/elasticsearch', + tarball: '/usr/local/var/data/elasticsearch', + }) + attribute(:path_logs, kind_of: Hash, default: { + package: '/var/log/elasticsearch', + tarball: '/usr/local/var/log/elasticsearch', + }) + attribute(:path_pid, kind_of: Hash, default: { + package: '/var/run/elasticsearch', + tarball: '/usr/local/var/run', + }) + attribute(:path_plugins, kind_of: Hash, default: { + package: '/usr/share/elasticsearch/plugins', + tarball: '/usr/local/elasticsearch/plugins', + }) + attribute(:path_bin, kind_of: Hash, default: { + package: '/usr/share/elasticsearch/bin', + tarball: '/usr/local/bin', + }) - attribute(:template_elasticsearch_env, kind_of: String, default: 'elasticsearch.in.sh.erb') - attribute(:template_elasticsearch_yml, kind_of: String, default: 'elasticsearch.yml.erb') - attribute(:template_logging_yml, kind_of: String, default: 'logging.yml.erb') + attribute(:template_elasticsearch_env, kind_of: String, default: 'elasticsearch.in.sh.erb') + attribute(:template_elasticsearch_yml, kind_of: String, default: 'elasticsearch.yml.erb') + attribute(:template_logging_yml, kind_of: String, default: 'logging.yml.erb') - attribute(:logging, kind_of: Hash, default: {}) + attribute(:logging, kind_of: Hash, default: {}) - attribute(:java_home, kind_of: String, default: nil) - attribute(:es_home, kind_of: String, default: '/usr/local') + attribute(:java_home, kind_of: String, default: nil) - # Calculations for this are done in the provider, as we can't do them in the - # resource definition. default is 50% of RAM or 31GB, which ever is smaller. - attribute(:allocated_memory, kind_of: String) + # Calculations for this are done in the provider, as we can't do them in the + # resource definition. default is 50% of RAM or 31GB, which ever is smaller. + attribute(:allocated_memory, kind_of: String) - attribute(:thread_stack_size, kind_of: String, default: '256k') - attribute(:env_options, kind_of: String, default: '') - attribute(:gc_settings, kind_of: String, default: - <<-CONFIG - -XX:+UseParNewGC - -XX:+UseConcMarkSweepGC - -XX:CMSInitiatingOccupancyFraction=75 - -XX:+UseCMSInitiatingOccupancyOnly - -XX:+HeapDumpOnOutOfMemoryError - -XX:+DisableExplicitGC - CONFIG - ) + attribute(:thread_stack_size, kind_of: String, default: '256k') + attribute(:env_options, kind_of: String, default: '') + attribute(:gc_settings, kind_of: String, default: + <<-CONFIG + -XX:+UseParNewGC + -XX:+UseConcMarkSweepGC + -XX:CMSInitiatingOccupancyFraction=75 + -XX:+UseCMSInitiatingOccupancyOnly + -XX:+HeapDumpOnOutOfMemoryError + -XX:+DisableExplicitGC + CONFIG + ) - # These are the default settings. Most of the time, you want to override - # the `configuration` attribute below. If you do override the defaults, you - # must supply ALL needed defaults, and don't use nil as a value in the hash. - attribute(:default_configuration, kind_of: Hash, default: { - # === NAMING - 'cluster.name' => 'elasticsearch', - # can't access node.name, so expect to have to set set this - 'node.name' => Chef::Config[:node_name], + # These are the default settings. Most of the time, you want to override + # the `configuration` attribute below. If you do override the defaults, you + # must supply ALL needed defaults, and don't use nil as a value in the hash. + attribute(:default_configuration, kind_of: Hash, default: { + # === NAMING + 'cluster.name' => 'elasticsearch', + # can't access node.name, so expect to have to set set this + 'node.name' => Chef::Config[:node_name], - 'path.conf' => nil, # default "/usr/local/etc/elasticsearch" - 'path.data' => nil, # default "/usr/local/var/data/elasticsearch" - 'path.logs' => nil, # default "/usr/local/var/log/elasticsearch" + 'path.conf' => nil, # default "/usr/local/etc/elasticsearch" + 'path.data' => nil, # default "/usr/local/var/data/elasticsearch" + 'path.logs' => nil, # default "/usr/local/var/log/elasticsearch" - 'action.destructive_requires_name' => true, - 'node.max_local_storage_nodes' => 1, + 'action.destructive_requires_name' => true, + 'node.max_local_storage_nodes' => 1, - 'discovery.zen.ping.multicast.enabled' => true, - 'discovery.zen.minimum_master_nodes' => 1, - 'gateway.expected_nodes' => 1, + 'discovery.zen.ping.multicast.enabled' => true, + 'discovery.zen.minimum_master_nodes' => 1, + 'gateway.expected_nodes' => 1, - 'http.port' => 9200 - }) + 'http.port' => 9200 + }) - # These settings are merged with the `default_configuration` attribute, - # allowing you to override and set specific settings. - # - attribute(:configuration, kind_of: Hash, default: {}) - end + # These settings are merged with the `default_configuration` attribute, + # allowing you to override and set specific settings. + # + attribute(:configuration, kind_of: Hash, default: {}) end diff --git a/libraries/resource_install.rb b/libraries/resource_install.rb index 2c1e1be3e..ca6301269 100644 --- a/libraries/resource_install.rb +++ b/libraries/resource_install.rb @@ -1,37 +1,38 @@ - -class Chef - # Chef Resource for installing or removing Elasticsearch from package or source - class Resource::ElasticsearchInstall < Chef::Resource::LWRPBase - resource_name :elasticsearch_install if respond_to?(:resource_name) - provides :elasticsearch_install - - actions(:install, :remove) - default_action :install - - # if this version parameter is not set by the caller, we look at - # `attributes/default.rb` for a default value to use, or we raise - attribute(:version, kind_of: String, default: nil) - - # we allow a string or symbol for this value - attribute(:type, kind_of: [Symbol, String], - :equal_to => ['tarball', 'tar', 'package', :tarball, :tar, :package], default: 'tarball') - - # these use `attributes/default.rb` for default values per platform and install type - attribute(:download_url, kind_of: String, default: nil) - attribute(:download_checksum, kind_of: String, default: nil) # sha256 - - # attributes used by the package-flavor provider - attribute(:package_options, kind_of: String, default: nil) - - # attributes used by the tarball-flavor provider - attribute(:dir, kind_of: String, default: nil) - attribute(:owner, kind_of: String, default: 'elasticsearch') - attribute(:group, kind_of: String, default: 'elasticsearch') - - # deprecated, use download_url and download_checksum - attribute(:package_url, kind_of: String, default: nil) - attribute(:package_checksum, kind_of: String, default: nil) # sha256 - attribute(:tarball_url, kind_of: String, default: nil) - attribute(:tarball_checksum, kind_of: String, default: nil) # sha256 - end +# Chef Resource for installing or removing Elasticsearch from package or source +class ElasticsearchCookbook::InstallResource < Chef::Resource::LWRPBase + resource_name :elasticsearch_install + provides :elasticsearch_install + + actions(:install, :remove) + default_action :install + + # this is what helps the various resources find each other + attribute(:instance_name, kind_of: String, default: nil) + + # if this version parameter is not set by the caller, we look at + # `attributes/default.rb` for a default value to use, or we raise + attribute(:version, kind_of: String, default: nil) + + # we allow a string or symbol for this value + attribute(:type, kind_of: [Symbol], + :equal_to => [:tarball, :package], default: :tarball) + + # these use `attributes/default.rb` for default values per platform and install type + attribute(:download_url, kind_of: String, default: nil) + attribute(:download_checksum, kind_of: String, default: nil) # sha256 + + # these correspond to :type of install + attribute(:dir, kind_of: Hash, default: { + package: '/usr/share', + tarball: '/usr/local' + }) + + # attributes used by the package-flavor provider + attribute(:package_options, kind_of: String, default: nil) + + # deprecated, use download_url and download_checksum + attribute(:package_url, kind_of: String, default: nil) + attribute(:package_checksum, kind_of: String, default: nil) # sha256 + attribute(:tarball_url, kind_of: String, default: nil) + attribute(:tarball_checksum, kind_of: String, default: nil) # sha256 end diff --git a/libraries/resource_plugin.rb b/libraries/resource_plugin.rb index 5e4167dcd..b7cb8f568 100644 --- a/libraries/resource_plugin.rb +++ b/libraries/resource_plugin.rb @@ -1,21 +1,17 @@ +# Chef Resource for installing an elasticsearch plugin +class ElasticsearchCookbook::PluginResource < Chef::Resource::LWRPBase + resource_name :elasticsearch_plugin + provides :elasticsearch_plugin -class Chef - # Chef Resource for installing an elasticsearch plugin - class Resource::ElasticsearchPlugin < Chef::Resource::LWRPBase - include ElasticsearchCookbook::Helpers - resource_name :elasticsearch_plugin if respond_to?(:resource_name) - actions(:install, :remove) - default_action :install + include ElasticsearchCookbook::Helpers - # You must override these for the package-installed version - attribute(:plugin_dir, kind_of: String, default: '/usr/local/elasticsearch/plugins') - attribute(:bindir, kind_of: String, default: '/usr/local/bin') + actions(:install, :remove) + default_action :install - attribute(:plugin_name, kind_of: String, :name_attribute => true) - attribute(:version, kind_of: String) - attribute(:url, kind_of: String) + # this is what helps the various resources find each other + attribute(:instance_name, kind_of: String, default: nil) - attribute(:user, kind_of: String, default: 'elasticsearch') - attribute(:group, kind_of: String, default: 'elasticsearch') - end + attribute(:plugin_name, kind_of: String, name_attribute: true) + attribute(:version, kind_of: String) + attribute(:url, kind_of: String) end diff --git a/libraries/resource_service.rb b/libraries/resource_service.rb index 5335c9f1a..3c517c3bd 100644 --- a/libraries/resource_service.rb +++ b/libraries/resource_service.rb @@ -1,32 +1,27 @@ +# Chef Resource for declaring a service for Elasticsearch +class ElasticsearchCookbook::ServiceResource < Chef::Resource::LWRPBase + resource_name :elasticsearch_service + provides :elasticsearch_service -class Chef - # Chef Resource for declaring a service for Elasticsearch - class Resource::ElasticsearchService < Chef::Resource::LWRPBase - resource_name :elasticsearch_service if respond_to?(:resource_name) - actions(:configure, :remove) - default_action :configure + actions(:configure, :remove) + default_action :configure - attribute(:service_name, kind_of: String, name_attribute: true) - attribute(:node_name, kind_of: String, default: Chef::Config[:node_name]) - attribute(:path_conf, kind_of: String, default: '/usr/local/etc/elasticsearch') - attribute(:bindir, kind_of: String, default: '/usr/local/bin') - attribute(:args, kind_of: String, default: '-d') + # this is what helps the various resources find each other + attribute(:instance_name, kind_of: String, default: nil) - attribute(:pid_path, kind_of: String, default: '/usr/local/var/run') - attribute(:pid_file, kind_of: String, default: nil) # default to pid_path/var/run/short_node_name.pid + attribute(:service_name, kind_of: String, name_attribute: true) + attribute(:args, kind_of: String, default: '-d') - attribute(:user, kind_of: String, name_attribute: true) # default to resource name - attribute(:group, kind_of: String, name_attribute: true) # default to resource name + attribute(:pid_file, kind_of: String, default: nil) # default to pid_path/var/run/short_node_name.pid - # default user limits - attribute(:memlock_limit, kind_of: String, default: 'unlimited') - attribute(:nofile_limit, kind_of: String, default: '64000') + # default user limits + attribute(:memlock_limit, kind_of: String, default: 'unlimited') + attribute(:nofile_limit, kind_of: String, default: '64000') - # service actions - attribute(:service_actions, kind_of: [Symbol, Array], default: [:enable]) + # service actions + attribute(:service_actions, kind_of: [Symbol, Array], default: [:enable]) - # allow overridable init script - attribute(:init_source, kind_of: String, default: 'elasticsearch.init.erb') - attribute(:init_cookbook, kind_of: String, default: 'elasticsearch') - end + # allow overridable init script + attribute(:init_source, kind_of: String, default: 'elasticsearch.init.erb') + attribute(:init_cookbook, kind_of: String, default: 'elasticsearch') end diff --git a/libraries/resource_user.rb b/libraries/resource_user.rb index eef628585..ca350f124 100644 --- a/libraries/resource_user.rb +++ b/libraries/resource_user.rb @@ -1,21 +1,23 @@ +# Chef Resource for declaring a user and group for Elasticsearch +class ElasticsearchCookbook::UserResource < Chef::Resource::LWRPBase + resource_name :elasticsearch_user + provides :elasticsearch_user -class Chef - # Chef Resource for declaring a user and group for Elasticsearch - class Resource::ElasticsearchUser < Chef::Resource::LWRPBase - resource_name :elasticsearch_user if respond_to?(:resource_name) - actions(:create, :remove) - default_action :create + actions(:create, :remove) + default_action :create - attribute(:username, kind_of: String, name_attribute: true) # default to resource name - attribute(:uid, kind_of: Integer) - attribute(:shell, kind_of: String, default: '/bin/bash') - attribute(:comment, kind_of: String, default: 'Elasticsearch User') + # this is what helps the various resources find each other + attribute(:instance_name, kind_of: String, default: nil) - attribute(:homedir_name, kind_of: String, name_attribute: true) # default to resource name - attribute(:homedir_parent, kind_of: String, default: '/usr/local') # windows requires override - attribute(:homedir, kind_of: String, default: nil) # defaults to ::File.join(homedir_parent, homedir_name) + attribute(:username, kind_of: String, name_attribute: true) # default to resource name + attribute(:uid, kind_of: Integer) + attribute(:shell, kind_of: String, default: '/bin/bash') + attribute(:comment, kind_of: String, default: 'Elasticsearch User') - attribute(:groupname, kind_of: String, name_attribute: true) # default to resource name - attribute(:gid, kind_of: Integer) - end + attribute(:homedir_name, kind_of: String, name_attribute: true) # default to resource name + attribute(:homedir_parent, kind_of: String, default: '/usr/local') # windows requires override + attribute(:homedir, kind_of: String, default: nil) # defaults to ::File.join(homedir_parent, homedir_name) + + attribute(:groupname, kind_of: String, name_attribute: true) # default to resource name + attribute(:gid, kind_of: Integer) end diff --git a/recipes/default.rb b/recipes/default.rb index c6afd60e1..bcd643530 100644 --- a/recipes/default.rb +++ b/recipes/default.rb @@ -9,7 +9,9 @@ # see README.md and test/fixtures/cookbooks for more examples! elasticsearch_user 'elasticsearch' -elasticsearch_install 'elasticsearch' +elasticsearch_install 'elasticsearch' do + type node['elasticsearch']['install_type'].to_sym # since TK can't symbol. +end elasticsearch_configure 'elasticsearch' elasticsearch_service 'elasticsearch' diff --git a/test/fixtures/cookbooks/elasticsearch_test/recipes/default.rb b/test/fixtures/cookbooks/elasticsearch_test/recipes/default.rb index db9a3b740..e50621a62 100644 --- a/test/fixtures/cookbooks/elasticsearch_test/recipes/default.rb +++ b/test/fixtures/cookbooks/elasticsearch_test/recipes/default.rb @@ -3,36 +3,30 @@ # create user with all non-default overriden options elasticsearch_user 'foobar' do - groupname 'bar' username 'foo' + groupname 'bar' uid 1111 gid 2222 shell '/bin/sh' homedir '/usr/local/myhomedir' -end - -elasticsearch_user 'deleteme' - -elasticsearch_user 'deleteme' do - action :remove -end - -# we're going to test both types on a single system! -elasticsearch_install 'elasticsearch_p' do - type :package + instance_name 'special_tarball_instance' end elasticsearch_install 'elasticsearch_s' do type :tarball - dir '/usr/local/awesome' - owner 'foo' - group 'bar' + dir tarball: '/usr/local/awesome' + instance_name 'special_tarball_instance' end elasticsearch_configure 'my_elasticsearch' do - dir '/usr/local/awesome' - user 'foo' - group 'bar' + + path_conf tarball: '/usr/local/awesome/etc/elasticsearch' + path_data tarball: '/usr/local/awesome/var/data/elasticsearch' + path_logs tarball: '/usr/local/awesome/var/log/elasticsearch' + path_pid tarball: '/usr/local/awesome/var/run' + path_plugins tarball: '/usr/local/awesome/elasticsearch/plugins' + path_bin tarball: '/usr/local/bin' + logging(:action => 'INFO') allocated_memory '123m' @@ -49,20 +43,17 @@ CONFIG configuration('node.name' => 'crazy') - action :manage + instance_name 'special_tarball_instance' end elasticsearch_plugin 'mobz/elasticsearch-head' do - user 'foo' - group 'bar' - plugin_dir '/usr/local/awesome/elasticsearch-1.7.3/plugins' + instance_name 'special_tarball_instance' end elasticsearch_service 'elasticsearch-crazy' do node_name 'crazy' - path_conf '/usr/local/awesome/etc/elasticsearch' - pid_path '/usr/local/awesome/var/run' - user 'foo' - group 'bar' + # path_conf '/usr/local/awesome/etc/elasticsearch' + # path_pid '/usr/local/awesome/var/run' + instance_name 'special_tarball_instance' end diff --git a/test/fixtures/cookbooks/elasticsearch_test/recipes/package.rb b/test/fixtures/cookbooks/elasticsearch_test/recipes/package.rb new file mode 100644 index 000000000..9429ec123 --- /dev/null +++ b/test/fixtures/cookbooks/elasticsearch_test/recipes/package.rb @@ -0,0 +1,50 @@ +# this is a test fixture used to test that the elasticsearch cookbook's +# resources, providers, and recipes can be used correctly from a wrapper + +# create user with all non-default overriden options +elasticsearch_user 'foobar' do + groupname 'bar' + username 'foo' + uid 1111 + gid 2222 + shell '/bin/sh' + homedir '/usr/local/myhomedir' + instance_name 'special_package_instance' +end + +# we're going to test both types on a single system! +elasticsearch_install 'elasticsearch_p' do + type :package + instance_name 'special_package_instance' +end + +elasticsearch_configure 'my_elasticsearch' do + logging(:action => 'INFO') + + allocated_memory '123m' + thread_stack_size '512k' + + env_options '-DFOO=BAR' + gc_settings <<-CONFIG + -XX:+UseParNewGC + -XX:+UseConcMarkSweepGC + -XX:CMSInitiatingOccupancyFraction=75 + -XX:+UseCMSInitiatingOccupancyOnly + -XX:+HeapDumpOnOutOfMemoryError + -XX:+PrintGCDetails + CONFIG + + configuration('node.name' => 'arbitrary_name') + # plugin_dir '/usr/local/awesome/elasticsearch-1.7.3/plugins' + action :manage + instance_name 'special_package_instance' +end + +elasticsearch_plugin 'mobz/elasticsearch-head' do + instance_name 'special_package_instance' +end + +elasticsearch_service 'elasticsearch-crazy' do + node_name 'arbitrary_name' + instance_name 'special_package_instance' +end diff --git a/test/fixtures/cookbooks/elasticsearch_test/recipes/user.rb b/test/fixtures/cookbooks/elasticsearch_test/recipes/user.rb new file mode 100644 index 000000000..539e64dc6 --- /dev/null +++ b/test/fixtures/cookbooks/elasticsearch_test/recipes/user.rb @@ -0,0 +1,7 @@ +elasticsearch_user 'deleteme' do + groupname 'foo' +end + +elasticsearch_user 'deleteme' do + action :remove +end diff --git a/test/integration/helpers/serverspec/configure_examples.rb b/test/integration/helpers/serverspec/configure_examples.rb index b996e1849..245b4c637 100644 --- a/test/integration/helpers/serverspec/configure_examples.rb +++ b/test/integration/helpers/serverspec/configure_examples.rb @@ -1,10 +1,10 @@ require_relative 'spec_helper' shared_examples_for 'elasticsearch configure' do |args = {}| - dir = args[:dir] || '/usr/local' - path_conf = args[:path_conf] || "#{dir}/etc/elasticsearch" - path_data = args[:path_data] || "#{dir}/var/data/elasticsearch" - path_logs = args[:path_logs] || "#{dir}/var/log/elasticsearch" + dir = args[:dir] || (package? ? '/usr/share/elasticsearch' : '/usr/local') + path_conf = args[:path_conf] || (package? ? '/etc/elasticsearch' : "#{dir}/etc/elasticsearch") + path_data = args[:path_data] || (package? ? '/var/lib/elasticsearch' : "#{dir}/var/data/elasticsearch") + path_logs = args[:path_logs] || (package? ? '/var/log/elasticsearch' : "#{dir}/var/log/elasticsearch") expected_user = args[:user] || 'elasticsearch' expected_group = args[:group] || expected_user || 'elasticsearch' diff --git a/test/integration/helpers/serverspec/install_examples.rb b/test/integration/helpers/serverspec/install_examples.rb index 3bdd3e6ff..f73362c74 100644 --- a/test/integration/helpers/serverspec/install_examples.rb +++ b/test/integration/helpers/serverspec/install_examples.rb @@ -1,31 +1,28 @@ require_relative 'spec_helper' shared_examples_for 'elasticsearch install' do |args = {}| - dir = args[:dir] || '/usr/local' + dir = args[:dir] || (package? ? '/usr/share/elasticsearch' : '/usr/local') version = args[:version] || '1.7.3' - package_name = args[:package] || false expected_user = args[:user] || 'elasticsearch' expected_group = args[:group] || expected_user || 'elasticsearch' - describe file("#{dir}/elasticsearch-#{version}") do + describe file("#{dir}/elasticsearch-#{version}"), :if => tarball? do it { should be_directory } it { should be_owned_by expected_user } it { should be_grouped_into expected_group } end - describe file("#{dir}/elasticsearch") do + describe file("#{dir}/elasticsearch"), :if => tarball? do it { should be_symlink } end - describe file('/usr/local/bin/elasticsearch') do + describe file('/usr/local/bin/elasticsearch'), :if => tarball? do it { should be_symlink } it { should be_linked_to("#{dir}/elasticsearch-#{version}/bin/elasticsearch") } end - if package_name - describe package(package_name) do - it { should be_installed } - end + describe package('elasticsearch'), :if => package? do + it { should be_installed } end end diff --git a/test/integration/helpers/serverspec/plugin_examples.rb b/test/integration/helpers/serverspec/plugin_examples.rb index 60965ab18..35df3878c 100644 --- a/test/integration/helpers/serverspec/plugin_examples.rb +++ b/test/integration/helpers/serverspec/plugin_examples.rb @@ -3,9 +3,10 @@ shared_examples_for 'elasticsearch plugin' do |plugin_name, args = {}| expected_user = args[:user] || 'elasticsearch' expected_group = args[:group] || expected_user || 'elasticsearch' - expected_home = args[:home] || "/usr/local/#{expected_user}" + expected_home = args[:home] || (package? ? "/usr/share/#{expected_user}" : "/usr/local/#{expected_user}") + expected_plugin = args[:plugin] || (package? ? "#{expected_home}/plugins/#{plugin_name}" : "#{expected_home}/plugins/#{plugin_name}") - describe file("#{expected_home}/plugins/#{plugin_name}") do + describe file(expected_plugin) do it { should be_directory } it { should be_owned_by expected_user } it { should be_grouped_into expected_group } diff --git a/test/integration/helpers/serverspec/spec_helper.rb b/test/integration/helpers/serverspec/spec_helper.rb index 328671972..838943843 100644 --- a/test/integration/helpers/serverspec/spec_helper.rb +++ b/test/integration/helpers/serverspec/spec_helper.rb @@ -7,3 +7,25 @@ set :backend, :exec set :path, '/usr/sbin:/sbin:/usr/local/sbin:/bin:/usr/bin:$PATH' + +def rhel? + %w(redhat).include?(os[:family]) +end + +def debian? + %w(ubuntu debian).include?(os[:family]) +end + +def package? + if debian? + system('dpkg -l elasticsearch >/dev/null 2>&1') + elsif rhel? + system('rpm -qa | grep elasticsearch >/dev/null 2>&1') + else + raise "I don't recognize #{os[:family]}, so I can't check for an elasticsearch package" + end +end + +def tarball? + return !package? +end diff --git a/test/integration/elasticsearch_test/serverspec/default_spec.rb b/test/integration/override_default/serverspec/default_spec.rb similarity index 100% rename from test/integration/elasticsearch_test/serverspec/default_spec.rb rename to test/integration/override_default/serverspec/default_spec.rb diff --git a/test/integration/override_package/serverspec/default_spec.rb b/test/integration/override_package/serverspec/default_spec.rb new file mode 100644 index 000000000..c21b94aa9 --- /dev/null +++ b/test/integration/override_package/serverspec/default_spec.rb @@ -0,0 +1,32 @@ +require_relative 'spec_helper' + +describe 'non-standard elasticsearch install and configure' do + it_behaves_like 'elasticsearch user', user: 'foo', + uid: 1111, + home: '/usr/local/myhomedir', + shell: '/bin/sh', + group: 'bar', + gid: 2222 + + it_behaves_like 'elasticsearch install', dir: '/usr/local/awesome', + package: 'elasticsearch', + user: 'foo', + group: 'bar' + + it_behaves_like 'elasticsearch configure', dir: '/usr/local/awesome', + user: 'foo', + group: 'bar', + env: ['FOO=BAR', 'PrintGCDetails', '-Xss512k'] + + it_behaves_like 'elasticsearch service', 'elasticsearch-crazy' +end + +describe 'removed elasticsearch users should NOT exist' do + describe group('deleteme') do + it { should_not exist } + end + + describe user('deleteme') do + it { should_not exist } + end +end diff --git a/test/unit/spec/install_spec.rb b/test/unit/spec/install_spec.rb index 544049844..e910c5056 100644 --- a/test/unit/spec/install_spec.rb +++ b/test/unit/spec/install_spec.rb @@ -18,10 +18,32 @@ _property = load_platform_properties(platform: platform, platform_version: version) it 'installs elasticsearch' do - expect(chef_run).to install_elasticsearch('elasticsearch_p') expect(chef_run).to install_elasticsearch('elasticsearch_s') end end end end end + +describe 'elasticsearch_test::package' do + before { stub_resources } + supported_platforms.each do |platform, versions| + versions.each do |version| + context "on #{platform.capitalize} #{version}" do + let(:chef_run) do + ChefSpec::ServerRunner.new(platform: platform, version: version, step_into: ['elasticsearch_install']) do |node, server| + node_resources(node) # data for this node + stub_chef_zero(platform, version, server) # stub other nodes in chef-zero + end.converge(described_recipe) + end + + # any platform specific data you want available to your test can be loaded here + _property = load_platform_properties(platform: platform, platform_version: version) + + it 'installs elasticsearch' do + expect(chef_run).to install_elasticsearch('elasticsearch_p') + end + end + end + end +end diff --git a/test/unit/spec/user_spec.rb b/test/unit/spec/user_spec.rb index a0a1b4c0e..4621bc378 100644 --- a/test/unit/spec/user_spec.rb +++ b/test/unit/spec/user_spec.rb @@ -2,7 +2,7 @@ require_relative 'spec_helper' -describe 'elasticsearch_test::default' do +describe 'elasticsearch_test::user' do before { stub_resources } supported_platforms.each do |platform, versions| versions.each do |version| @@ -17,8 +17,8 @@ # any platform specific data you want available to your test can be loaded here _property = load_platform_properties(platform: platform, platform_version: version) - it 'creates elasticsearch user foo in group bar' do - expect(chef_run).to create_elasticsearch_user('foobar') + it 'creates elasticsearch user deleteme in group foo' do + expect(chef_run).to create_elasticsearch_user('deleteme') end it 'deletes user deleteme' do