diff --git a/lib/facter/es_facts.rb b/lib/facter/es_facts.rb index e487775..002f289 100644 --- a/lib/facter/es_facts.rb +++ b/lib/facter/es_facts.rb @@ -1,135 +1,136 @@ # frozen_string_literal: true require 'net/http' require 'json' require 'yaml' # Helper module to encapsulate custom fact injection module EsFacts # Add a fact to the catalog of host facts def self.add_fact(prefix, key, value) key = "#{prefix}_#{key}".to_sym ::Facter.add(key) do setcode { value } end end def self.ssl?(config) tls_keys = [ 'xpack.security.http.ssl.enabled' ] tls_keys.any? { |key| (config.key? key) && (config[key] == true) } end # Helper to determine the instance http.port number def self.get_httpport(config) enabled = 'http.enabled' httpport = 'http.port' return false, false if !config[enabled].nil? && config[enabled] == 'false' return config[httpport], ssl?(config) unless config[httpport].nil? ['9200', ssl?(config)] end # Entrypoint for custom fact populator # # This is a super old function but works; disable a bunch of checks. def self.run dir_prefix = '/etc/elasticsearch' # httpports is a hash of port_number => ssl? transportports = [] http_bound_addresses = [] transport_bound_addresses = [] transport_publish_addresses = [] nodes = {} # only when the directory exists we need to process the stuff return unless File.directory?(dir_prefix) if File.readable?("#{dir_prefix}/elasticsearch.yml") config_data = YAML.load_file("#{dir_prefix}/elasticsearch.yml") httpport, ssl = get_httpport(config_data) end begin add_fact('elasticsearch', 'port', httpport) unless ssl key_prefix = 'elasticsearch' # key_prefix = "elasticsearch_#{httpport}" uri = URI("http://localhost:#{httpport}") http = Net::HTTP.new(uri.host, uri.port) http.read_timeout = 10 http.open_timeout = 2 response = http.get('/') json_data = JSON.parse(response.body) if json_data['status'] && json_data['status'] == 200 add_fact(key_prefix, 'name', json_data['name']) add_fact(key_prefix, 'version', json_data['version']['number']) uri2 = URI("http://localhost:#{httpport}/_nodes/#{json_data['name']}") http2 = Net::HTTP.new(uri2.host, uri2.port) http2.read_timeout = 10 http2.open_timeout = 2 response2 = http2.get(uri2.path) json_data_node = JSON.parse(response2.body) add_fact(key_prefix, 'cluster_name', json_data_node['cluster_name']) node_data = json_data_node['nodes'].first add_fact(key_prefix, 'node_id', node_data[0]) nodes_data = json_data_node['nodes'][node_data[0]] process = nodes_data['process'] add_fact(key_prefix, 'mlockall', process['mlockall']) plugins = nodes_data['plugins'] plugin_names = [] plugins.each do |plugin| plugin_names << plugin['name'] plugin.each do |key, value| prefix = "#{key_prefix}_plugin_#{plugin['name']}" add_fact(prefix, key, value) unless key == 'name' end end add_fact(key_prefix, 'plugins', plugin_names.join(',')) nodes_data['http']['bound_address'].each { |i| http_bound_addresses << i } nodes_data['transport']['bound_address'].each { |i| transport_bound_addresses << i } transport_publish_addresses << nodes_data['transport']['publish_address'] unless nodes_data['transport']['publish_address'].nil? transportports << nodes_data['settings']['transport']['tcp']['port'] unless nodes_data['settings']['transport']['tcp'].nil? || nodes_data['settings']['transport']['tcp']['port'].nil? node = { 'http_ports' => httpports.keys, 'transport_ports' => transportports, 'http_bound_addresses' => http_bound_addresses, 'transport_bound_addresses' => transport_bound_addresses, 'transport_publish_addresses' => transport_publish_addresses, json_data['name'] => { 'settings' => nodes_data['settings'], 'http' => nodes_data['http'], 'transport' => nodes_data['transport'] } } nodes.merge! node end end rescue StandardError + # ignore end Facter.add(:elasticsearch) do setcode do nodes end nodes unless nodes.empty? end end end EsFacts.run diff --git a/lib/puppet/provider/elastic_parsedfile.rb b/lib/puppet/provider/elastic_parsedfile.rb index b5a3dc8..c13e6a5 100644 --- a/lib/puppet/provider/elastic_parsedfile.rb +++ b/lib/puppet/provider/elastic_parsedfile.rb @@ -1,14 +1,14 @@ # frozen_string_literal: true require 'puppet/provider/parsedfile' # Parent class for Elasticsearch-based providers that need to access # specific configuration directories. class Puppet::Provider::ElasticParsedFile < Puppet::Provider::ParsedFile # Find/set an x-pack configuration file. # # @return String def self.xpack_config(val) - @default_target ||= "/etc/elasticsearch/#{val}" + @xpack_config ||= "/etc/elasticsearch/#{val}" end end diff --git a/lib/puppet/provider/elastic_rest.rb b/lib/puppet/provider/elastic_rest.rb index 09e7c79..f320b0e 100644 --- a/lib/puppet/provider/elastic_rest.rb +++ b/lib/puppet/provider/elastic_rest.rb @@ -1,286 +1,286 @@ # frozen_string_literal: true require 'json' require 'net/http' require 'openssl' # Parent class encapsulating general-use functions for children REST-based # providers. class Puppet::Provider::ElasticREST < Puppet::Provider class << self attr_accessor :api_discovery_uri, :api_resource_style, :api_uri, :discrete_resource_creation, :metadata, :metadata_pipeline, :query_string end # Fetch arbitrary metadata for the class from an instance object. # # @return String def metadata self.class.metadata end # Retrieve the class query_string variable # # @return String def query_string self.class.query_string end # Perform a REST API request against the indicated endpoint. # # @return Net::HTTPResponse - def self.rest(http, \ - req, \ - validate_tls = true, \ - timeout = 10, \ - username = nil, \ - password = nil) + def self.rest(http, + req, + timeout = 10, + username = nil, + password = nil, + validate_tls: true) if username && password req.basic_auth username, password elsif username || password Puppet.warning( 'username and password must both be defined, skipping basic auth' ) end req['Accept'] = 'application/json' http.read_timeout = timeout http.open_timeout = timeout http.verify_mode = OpenSSL::SSL::VERIFY_NONE unless validate_tls begin http.request req rescue EOFError => e # Because the provider attempts a best guess at API access, we # only fail when HTTP operations fail for mutating methods. unless %w[GET OPTIONS HEAD].include? req.method raise Puppet::Error, "Received '#{e}' from the Elasticsearch API. Are your API settings correct?" end end end # Helper to format a remote URL request for Elasticsearch which takes into # account path ordering, et cetera. def self.format_uri(resource_path, property_flush = {}) return api_uri if resource_path.nil? || api_resource_style == :bare if discrete_resource_creation && !property_flush[:ensure].nil? resource_path else case api_resource_style when :prefix "#{resource_path}/#{api_uri}" else "#{api_uri}/#{resource_path}" end end end # Fetch Elasticsearch API objects. Accepts a variety of argument functions # dictating how to connect to the Elasticsearch API. # # @return Array # an array of Hashes representing the found API objects, whether they be # templates, pipelines, et cetera. - def self.api_objects(protocol = 'http', \ - validate_tls = true, \ - host = 'localhost', \ - port = 9200, \ - timeout = 10, \ - username = nil, \ - password = nil, \ - ca_file = nil, \ - ca_path = nil) + def self.api_objects(protocol = 'http', + host = 'localhost', + port = 9200, + timeout = 10, + username = nil, + password = nil, + ca_file = nil, + ca_path = nil, + validate_tls: true) uri = URI("#{protocol}://#{host}:#{port}/#{format_uri(api_discovery_uri)}") http = Net::HTTP.new uri.host, uri.port req = Net::HTTP::Get.new uri.request_uri http.use_ssl = uri.scheme == 'https' [[ca_file, :ca_file=], [ca_path, :ca_path=]].each do |arg, method| http.send method, arg if arg && http.respond_to?(method) end - response = rest http, req, validate_tls, timeout, username, password + response = rest http, req, timeout, username, password, validate_tls: validate_tls results = [] results = process_body(response.body) if response.respond_to?(:code) && response.code.to_i == 200 results end # Process the JSON response body def self.process_body(body) JSON.parse(body).map do |object_name, api_object| { :name => object_name, :ensure => :present, metadata => process_metadata(api_object), :provider => name } end end # Passes API objects through arbitrary Procs/lambdas in order to postprocess # API responses. def self.process_metadata(raw_metadata) if metadata_pipeline.is_a?(Array) && !metadata_pipeline.empty? metadata_pipeline.reduce(raw_metadata) do |md, processor| processor.call md end else raw_metadata end end # Fetch an array of provider objects from the Elasticsearch API. def self.instances api_objects.map { |resource| new resource } end # Unlike a typical #prefetch, which just ties discovered #instances to the # correct resources, we need to quantify all the ways the resources in the # catalog know about Elasticsearch API access and use those settings to # fetch any templates we can before associating resources and providers. def self.prefetch(resources) # Get all relevant API access methods from the resources we know about res = resources.map do |_, resource| p = resource.parameters [ p[:protocol].value, - p[:validate_tls].value, p[:host].value, p[:port].value, p[:timeout].value, (p.key?(:username) ? p[:username].value : nil), (p.key?(:password) ? p[:password].value : nil), (p.key?(:ca_file) ? p[:ca_file].value : nil), - (p.key?(:ca_path) ? p[:ca_path].value : nil) + (p.key?(:ca_path) ? p[:ca_path].value : nil), + p[:validate_tls].value, ] # Deduplicate identical settings, and fetch templates end.uniq res = res.map do |api| api_objects(*api) # Flatten and deduplicate the array, instantiate providers, and do the # typical association dance end res.flatten.uniq.map { |resource| new resource }.each do |prov| if (resource = resources[prov.name]) resource.provider = prov end end end def initialize(value = {}) super(value) @property_flush = {} end # Generate a request body def generate_body JSON.generate( if metadata != :content && @property_flush[:ensure] == :present { metadata.to_s => resource[metadata] } else resource[metadata] end ) end # Call Elasticsearch's REST API to appropriately PUT/DELETE/or otherwise # update any managed API objects. def flush Puppet.debug('Got to flush') uri = URI( format( '%s://%s:%d/%s', resource[:protocol], resource[:host], resource[:port], self.class.format_uri(resource[:name], @property_flush) ) ) uri.query = URI.encode_www_form query_string if query_string Puppet.debug("Generated URI = #{uri.inspect}") case @property_flush[:ensure] when :absent req = Net::HTTP::Delete.new uri.request_uri else req = Net::HTTP::Put.new uri.request_uri req.body = generate_body Puppet.debug("Generated body looks like: #{req.body.inspect}") # As of Elasticsearch 6.x, required when requesting with a payload (so we # set it always to be safe) req['Content-Type'] = 'application/json' if req['Content-Type'].nil? end http = Net::HTTP.new uri.host, uri.port http.use_ssl = uri.scheme == 'https' %i[ca_file ca_path].each do |arg| http.send "#{arg}=".to_sym, resource[arg] if !resource[arg].nil? && http.respond_to?(arg) end response = self.class.rest( http, req, - resource[:validate_tls], resource[:timeout], resource[:username], - resource[:password] + resource[:password], + validate_tls: resource[:validate_tls] ) # Attempt to return useful error output unless response.code.to_i == 200 Puppet.debug("Non-OK reponse: Body = #{response.body.inspect}") json = JSON.parse(response.body) err_msg = if json.key? 'error' if json['error'].is_a?(Hash) \ && json['error'].key?('root_cause') # Newer versions have useful output json['error']['root_cause'].first['reason'] else # Otherwise fallback to old-style error messages json['error'] end else # As a last resort, return the response error code "HTTP #{response.code}" end raise Puppet::Error, "Elasticsearch API responded with: #{err_msg}" end @property_hash = self.class.api_objects( resource[:protocol], - resource[:validate_tls], resource[:host], resource[:port], resource[:timeout], resource[:username], resource[:password], resource[:ca_file], - resource[:ca_path] + resource[:ca_path], + validate_tls: resource[:validate_tls] ).find do |t| t[:name] == resource[:name] end end # Set this provider's `:ensure` property to `:present`. def create @property_flush[:ensure] = :present end def exists? @property_hash[:ensure] == :present end # Set this provider's `:ensure` property to `:absent`. def destroy @property_flush[:ensure] = :absent end end diff --git a/lib/puppet/type/elasticsearch_index.rb b/lib/puppet/type/elasticsearch_index.rb index fef6803..4d7aa7f 100644 --- a/lib/puppet/type/elasticsearch_index.rb +++ b/lib/puppet/type/elasticsearch_index.rb @@ -1,36 +1,36 @@ # frozen_string_literal: true $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', '..')) require 'puppet_x/elastic/asymmetric_compare' require 'puppet_x/elastic/deep_to_i' require 'puppet_x/elastic/deep_to_s' require 'puppet_x/elastic/elasticsearch_rest_resource' Puppet::Type.newtype(:elasticsearch_index) do extend ElasticsearchRESTResource desc 'Manages Elasticsearch index settings.' ensurable newparam(:name, namevar: true) do desc 'Index name.' end newproperty(:settings) do desc 'Structured settings for the index in hash form.' - def insync?(is) - Puppet_X::Elastic.asymmetric_compare(should, is) + def insync?(value) + Puppet_X::Elastic.asymmetric_compare(should, value) end munge do |value| Puppet_X::Elastic.deep_to_i(Puppet_X::Elastic.deep_to_s(value)) end validate do |value| raise Puppet::Error, 'hash expected' unless value.is_a? Hash end end end diff --git a/lib/puppet/type/elasticsearch_keystore.rb b/lib/puppet/type/elasticsearch_keystore.rb index 7bae2a0..6f87b6e 100644 --- a/lib/puppet/type/elasticsearch_keystore.rb +++ b/lib/puppet/type/elasticsearch_keystore.rb @@ -1,66 +1,66 @@ # frozen_string_literal: true require 'puppet/parameter/boolean' Puppet::Type.newtype(:elasticsearch_keystore) do desc 'Manages an Elasticsearch keystore settings file.' ensurable newparam(:instance, namevar: true) do desc 'Elasticsearch instance this keystore belongs to.' end newparam(:configdir) do desc 'Path to the elasticsearch configuration directory (ES_PATH_CONF).' defaultto '/etc/elasticsearch' end newparam(:purge, boolean: true, parent: Puppet::Parameter::Boolean) do desc <<-EOS Whether to proactively remove settings that exist in the keystore but are not present in this resource's settings. EOS defaultto false end newproperty(:settings, array_matching: :all) do desc 'A key/value hash of settings names and values.' # The keystore utility can only retrieve a list of stored settings, # so here we only compare the existing settings (sorted) with the # desired settings' keys - def insync?(is) + def insync?(value) if resource[:purge] - is.sort == @should.first.keys.sort + value.sort == @should.first.keys.sort else - (@should.first.keys - is).empty? + (@should.first.keys - value).empty? end end def change_to_s(currentvalue, newvalue_raw) ret = '' newvalue = newvalue_raw.first.keys added_settings = newvalue - currentvalue ret << "added: #{added_settings.join(', ')} " unless added_settings.empty? removed_settings = currentvalue - newvalue unless removed_settings.empty? ret << if resource[:purge] "removed: #{removed_settings.join(', ')}" else "would have removed: #{removed_settings.join(', ')}, but purging is disabled" end end ret end end autorequire(:augeas) do "defaults_#{self[:name]}" end end diff --git a/lib/puppet/type/elasticsearch_license.rb b/lib/puppet/type/elasticsearch_license.rb index 62890d2..0e43f30 100644 --- a/lib/puppet/type/elasticsearch_license.rb +++ b/lib/puppet/type/elasticsearch_license.rb @@ -1,51 +1,51 @@ # frozen_string_literal: true $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', '..')) require 'puppet_x/elastic/asymmetric_compare' require 'puppet_x/elastic/deep_to_i' require 'puppet_x/elastic/deep_to_s' require 'puppet_x/elastic/elasticsearch_rest_resource' Puppet::Type.newtype(:elasticsearch_license) do extend ElasticsearchRESTResource desc 'Manages Elasticsearch licenses.' ensurable newparam(:name, namevar: true) do desc 'Pipeline name.' end newproperty(:content) do desc 'Structured hash for license content data.' - def insync?(is) + def insync?(value) Puppet_X::Elastic.asymmetric_compare( should.transform_values { |v| v.is_a?(Hash) ? (v.reject { |s, _| s == 'signature' }) : v }, - is + value ) end def should_to_s(newvalue) newvalue.transform_values do |license_data| if license_data.is_a? Hash license_data.map do |field, value| [field, field == 'signature' ? '[redacted]' : value] end.to_h else v end end.to_s end validate do |value| raise Puppet::Error, 'hash expected' unless value.is_a? Hash end munge do |value| Puppet_X::Elastic.deep_to_i(Puppet_X::Elastic.deep_to_s(value)) end end end diff --git a/lib/puppet/type/elasticsearch_template.rb b/lib/puppet/type/elasticsearch_template.rb index 8ebf0ca..bc37b59 100644 --- a/lib/puppet/type/elasticsearch_template.rb +++ b/lib/puppet/type/elasticsearch_template.rb @@ -1,116 +1,117 @@ # frozen_string_literal: true $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', '..')) require 'puppet/file_serving/content' require 'puppet/file_serving/metadata' require 'puppet_x/elastic/deep_implode' require 'puppet_x/elastic/deep_to_i' require 'puppet_x/elastic/deep_to_s' require 'puppet_x/elastic/elasticsearch_rest_resource' Puppet::Type.newtype(:elasticsearch_template) do extend ElasticsearchRESTResource desc 'Manages Elasticsearch index templates.' ensurable newparam(:name, namevar: true) do desc 'Template name.' end newproperty(:content) do desc 'Structured content of template.' validate do |value| raise Puppet::Error, 'hash expected' unless value.is_a? Hash end munge do |value| # The Elasticsearch API will return default empty values for # order, aliases, and mappings if they aren't defined in the # user mapping, so we need to set defaults here to keep the # `in` and `should` states consistent if the user hasn't # provided any. # # The value is first stringified, then integers are parse out as # necessary, since the Elasticsearch API enforces some fields to be # integers. # # We also need to fully qualify index settings, since users # can define those with the index json key absent, but the API # always fully qualifies them. { 'order' => 0, 'aliases' => {}, 'mappings' => {} }.merge( Puppet_X::Elastic.deep_to_i( Puppet_X::Elastic.deep_to_s( value.tap do |val| if val.key? 'settings' val['settings']['index'] = {} unless val['settings'].key? 'index' (val['settings'].keys - ['index']).each do |setting| new_key = if setting.start_with? 'index.' setting[6..-1] else setting end val['settings']['index'][new_key] = \ val['settings'].delete setting end end end ) ) ) end - def insync?(is) - Puppet_X::Elastic.deep_implode(is) == \ + def insync?(value) + Puppet_X::Elastic.deep_implode(value) == \ Puppet_X::Elastic.deep_implode(should) end end newparam(:source) do desc 'Puppet source to file containing template contents.' validate do |value| raise Puppet::Error, 'string expected' unless value.is_a? String end end # rubocop:disable Style/SignalException validate do # Ensure that at least one source of template content has been provided if self[:ensure] == :present fail Puppet::ParseError, '"content" or "source" required' \ if self[:content].nil? && self[:source].nil? if !self[:content].nil? && !self[:source].nil? fail( Puppet::ParseError, "'content' and 'source' cannot be simultaneously defined" ) end end # If a source was passed, retrieve the source content from Puppet's # FileServing indirection and set the content property unless self[:source].nil? fail(format('Could not retrieve source %s', self[:source])) unless Puppet::FileServing::Metadata.indirection.find(self[:source]) tmp = if !catalog.nil? \ && catalog.respond_to?(:environment_instance) Puppet::FileServing::Content.indirection.find( self[:source], environment: catalog.environment_instance ) else Puppet::FileServing::Content.indirection.find(self[:source]) end fail(format('Could not find any content at %s', self[:source])) unless tmp self[:content] = PSON.load(tmp.content) end end + # rubocop:enable Style/SignalException end diff --git a/lib/puppet/type/elasticsearch_user_roles.rb b/lib/puppet/type/elasticsearch_user_roles.rb index ced8439..28cf661 100644 --- a/lib/puppet/type/elasticsearch_user_roles.rb +++ b/lib/puppet/type/elasticsearch_user_roles.rb @@ -1,22 +1,22 @@ # frozen_string_literal: true Puppet::Type.newtype(:elasticsearch_user_roles) do desc 'Type to model Elasticsearch user roles.' ensurable newparam(:name, namevar: true) do desc 'User name.' end newproperty(:roles, array_matching: :all) do desc 'Array of roles that the user should belong to.' - def insync?(is) - is.sort == should.sort + def insync?(value) + value.sort == should.sort end end autorequire(:elasticsearch_user) do self[:name] end end diff --git a/lib/puppet_x/elastic/plugin_parsing.rb b/lib/puppet_x/elastic/plugin_parsing.rb index 0d96e66..ca5aa1a 100644 --- a/lib/puppet_x/elastic/plugin_parsing.rb +++ b/lib/puppet_x/elastic/plugin_parsing.rb @@ -1,38 +1,38 @@ # frozen_string_literal: true class ElasticPluginParseFailure < StandardError; end module Puppet_X # rubocop:disable Style/ClassAndModuleCamelCase # Custom functions for plugin string parsing. module Elastic def self.plugin_name(raw_name) plugin_split(raw_name, 1) end def self.plugin_version(raw_name) v = plugin_split(raw_name, 2, false).gsub(%r{^[^0-9]*}, '') raise ElasticPluginParseFailure, "could not parse version, got '#{v}'" if v.empty? v end # Attempt to guess at the plugin's final directory name - def self.plugin_split(original_string, position, soft_fail = true) + def self.plugin_split(original_string, position, soft_fail: true) # Try both colon (maven) and slash-delimited (github/elastic.co) names %w[/ :].each do |delimiter| parts = original_string.split(delimiter) # If the string successfully split, assume we found the right format return parts[position].gsub(%r{(elasticsearch-|es-)}, '') unless parts[position].nil? end unless soft_fail raise( ElasticPluginParseFailure, "could not find element '#{position}' in #{original_string}" ) end original_string end end end diff --git a/spec/classes/000_elasticsearch_init_spec.rb b/spec/classes/000_elasticsearch_init_spec.rb index fa9daa9..34b8ad5 100644 --- a/spec/classes/000_elasticsearch_init_spec.rb +++ b/spec/classes/000_elasticsearch_init_spec.rb @@ -1,523 +1,525 @@ # frozen_string_literal: true require 'spec_helper' describe 'elasticsearch', type: 'class' do default_params = { config: { 'node.name' => 'foo' } } + # rubocop:disable RSpec/MultipleMemoizedHelpers on_supported_os.each do |os, facts| context "on #{os}" do case facts[:os]['family'] when 'Debian' let(:defaults_path) { '/etc/default' } let(:system_service_folder) { '/lib/systemd/system' } let(:pkg_ext) { 'deb' } let(:pkg_prov) { 'dpkg' } let(:version_add) { '' } if (facts[:os]['name'] == 'Debian' && \ facts[:os]['release']['major'].to_i >= 8) || \ (facts[:os]['name'] == 'Ubuntu' && \ facts[:os]['release']['major'].to_i >= 15) let(:systemd_service_path) { '/lib/systemd/system' } test_pid = true else test_pid = false end when 'RedHat' let(:defaults_path) { '/etc/sysconfig' } let(:system_service_folder) { '/lib/systemd/system' } let(:pkg_ext) { 'rpm' } let(:pkg_prov) { 'rpm' } let(:version_add) { '-1' } if facts[:os]['release']['major'].to_i >= 7 let(:systemd_service_path) { '/lib/systemd/system' } test_pid = true else test_pid = false end when 'Suse' let(:defaults_path) { '/etc/sysconfig' } let(:pkg_ext) { 'rpm' } let(:pkg_prov) { 'rpm' } let(:version_add) { '-1' } if facts[:os]['name'] == 'OpenSuSE' && facts[:os]['release']['major'].to_i <= 12 let(:systemd_service_path) { '/lib/systemd/system' } else let(:systemd_service_path) { '/usr/lib/systemd/system' } end end let(:facts) do facts.merge('scenario' => '', 'common' => '', 'elasticsearch' => {}) end let(:params) do default_params.merge({}) end it { is_expected.to compile.with_all_deps } # Varies depending on distro it { is_expected.to contain_augeas("#{defaults_path}/elasticsearch") } # Systemd-specific files if test_pid == true it { expect(subject).to contain_service('elasticsearch').with( ensure: 'running', enable: true ) } end context 'java installation' do let(:pre_condition) do <<-MANIFEST include ::java MANIFEST end it { expect(subject).to contain_class('elasticsearch::config'). that_requires('Class[java]') } end context 'package installation' do context 'via repository' do context 'with specified version' do let(:params) do default_params.merge( version: '1.0' ) end it { expect(subject).to contain_package('elasticsearch'). with(ensure: "1.0#{version_add}") } end if facts[:os]['family'] == 'RedHat' context 'Handle special CentOS/RHEL package versioning' do let(:params) do default_params.merge( version: '1.1-2' ) end it { expect(subject).to contain_package('elasticsearch'). with(ensure: '1.1-2') } end end end context 'when setting package version and package_url' do let(:params) do default_params.merge( version: '0.90.10', package_url: "puppet:///path/to/some/es-0.90.10.#{pkg_ext}" ) end it { is_expected.to raise_error(Puppet::Error) } end context 'via package_url setting' do ['file:/', 'ftp://', 'http://', 'https://', 'puppet:///'].each do |schema| context "using #{schema} schema" do let(:params) do default_params.merge( package_url: "#{schema}domain-or-path/pkg.#{pkg_ext}" ) end unless schema.start_with? 'puppet' it { expect(subject).to contain_exec('create_package_dir_elasticsearch'). with(command: 'mkdir -p /opt/elasticsearch/swdl') } it { expect(subject).to contain_file('/opt/elasticsearch/swdl'). with( purge: false, force: false, require: 'Exec[create_package_dir_elasticsearch]' ) } end case schema when 'file:/' it { expect(subject).to contain_file( "/opt/elasticsearch/swdl/pkg.#{pkg_ext}" ).with( source: "/domain-or-path/pkg.#{pkg_ext}", backup: false ) } when 'puppet:///' it { expect(subject).to contain_file( "/opt/elasticsearch/swdl/pkg.#{pkg_ext}" ).with( source: "#{schema}domain-or-path/pkg.#{pkg_ext}", backup: false ) } else [true, false].each do |verify_certificates| context "with download_tool_verify_certificates '#{verify_certificates}'" do let(:params) do default_params.merge( package_url: "#{schema}domain-or-path/pkg.#{pkg_ext}", download_tool_verify_certificates: verify_certificates ) end flag = verify_certificates ? '' : ' --no-check-certificate' it { expect(subject).to contain_exec('download_package_elasticsearch'). with( command: "wget#{flag} -O /opt/elasticsearch/swdl/pkg.#{pkg_ext} #{schema}domain-or-path/pkg.#{pkg_ext} 2> /dev/null", require: 'File[/opt/elasticsearch/swdl]' ) } end end end it { expect(subject).to contain_package('elasticsearch'). with( ensure: 'present', source: "/opt/elasticsearch/swdl/pkg.#{pkg_ext}", provider: pkg_prov ) } end end context 'using http:// schema with proxy_url' do let(:params) do default_params.merge( package_url: "http://www.domain.com/package.#{pkg_ext}", proxy_url: 'http://proxy.example.com:12345/' ) end it { expect(subject).to contain_exec('download_package_elasticsearch'). with( environment: [ 'use_proxy=yes', 'http_proxy=http://proxy.example.com:12345/', 'https_proxy=http://proxy.example.com:12345/' ] ) } end end end context 'when setting the module to absent' do let(:params) do default_params.merge( ensure: 'absent' ) end case facts[:os]['family'] when 'Suse' it { expect(subject).to contain_package('elasticsearch'). with(ensure: 'absent') } else it { expect(subject).to contain_package('elasticsearch'). with(ensure: 'purged') } end it { expect(subject).to contain_service('elasticsearch'). with( ensure: 'stopped', enable: 'false' ) } it { expect(subject).to contain_file('/usr/share/elasticsearch/plugins'). with(ensure: 'absent') } it { expect(subject).to contain_file("#{defaults_path}/elasticsearch"). with(ensure: 'absent') } end context 'When managing the repository' do let(:params) do default_params.merge( manage_repo: true ) end it { is_expected.to contain_class('elastic_stack::repo') } end context 'When not managing the repository' do let(:params) do default_params.merge( manage_repo: false ) end it { is_expected.to compile.with_all_deps } end end end + # rubocop:enable RSpec/MultipleMemoizedHelpers on_supported_os( hardwaremodels: ['x86_64'], supported_os: [ { 'operatingsystem' => 'CentOS', 'operatingsystemrelease' => ['7'] } ] ).each do |os, facts| context "on #{os}" do let(:facts) do facts.merge( scenario: '', common: '' ) end describe 'main class tests' do # init.pp it { is_expected.to compile.with_all_deps } it { is_expected.to contain_class('elasticsearch') } it { is_expected.to contain_class('elasticsearch::package') } it { expect(subject).to contain_class('elasticsearch::config'). that_requires('Class[elasticsearch::package]') } it { expect(subject).to contain_class('elasticsearch::service'). that_requires('Class[elasticsearch::config]') } # Base directories it { is_expected.to contain_file('/etc/elasticsearch') } it { is_expected.to contain_file('/usr/share/elasticsearch') } it { is_expected.to contain_file('/usr/share/elasticsearch/lib') } it { is_expected.to contain_file('/var/lib/elasticsearch') } it { is_expected.to contain_exec('remove_plugin_dir') } end context 'package installation' do describe 'with default package' do it { expect(subject).to contain_package('elasticsearch'). with(ensure: 'present') } it { expect(subject).not_to contain_package('my-elasticsearch'). with(ensure: 'present') } end describe 'with specified package name' do let(:params) do default_params.merge( package_name: 'my-elasticsearch' ) end it { expect(subject).to contain_package('elasticsearch'). with(ensure: 'present', name: 'my-elasticsearch') } it { expect(subject).not_to contain_package('elasticsearch'). with(ensure: 'present', name: 'elasticsearch') } end describe 'with auto upgrade enabled' do let(:params) do default_params.merge( autoupgrade: true ) end it { expect(subject).to contain_package('elasticsearch'). with(ensure: 'latest') } end end describe 'running a a different user' do let(:params) do default_params.merge( elasticsearch_user: 'myesuser', elasticsearch_group: 'myesgroup' ) end it { expect(subject).to contain_file('/etc/elasticsearch'). with(owner: 'myesuser', group: 'myesgroup') } it { expect(subject).to contain_file('/var/log/elasticsearch'). with(owner: 'myesuser') } it { expect(subject).to contain_file('/usr/share/elasticsearch'). with(owner: 'myesuser', group: 'myesgroup') } it { expect(subject).to contain_file('/var/lib/elasticsearch'). with(owner: 'myesuser', group: 'myesgroup') } end describe 'setting jvm_options' do jvm_options = [ '-Xms16g', '-Xmx16g' ] let(:params) do default_params.merge( jvm_options: jvm_options ) end jvm_options.each do |jvm_option| it { expect(subject).to contain_file_line("jvm_option_#{jvm_option}"). with( ensure: 'present', path: '/etc/elasticsearch/jvm.options', line: jvm_option ) } end end context 'with restart_on_change => true' do let(:params) do default_params.merge( restart_on_change: true ) end describe 'should restart elasticsearch' do it { expect(subject).to contain_file('/etc/elasticsearch/elasticsearch.yml'). that_notifies('Service[elasticsearch]') } end describe 'setting jvm_options triggers restart' do let(:params) do super().merge( jvm_options: ['-Xmx16g'] ) end it { expect(subject).to contain_file_line('jvm_option_-Xmx16g'). that_notifies('Service[elasticsearch]') } end end # This check helps catch dependency cycles. context 'create_resource' do # Helper for these tests - def singular(s) - case s + def singular(string) + case string when 'indices' 'index' when 'snapshot_repositories' 'snapshot_repository' else - s[0..-2] + string[0..-2] end end { 'indices' => { 'test-index' => {} }, # 'instances' => { 'es-instance' => {} }, 'pipelines' => { 'testpipeline' => { 'content' => {} } }, 'plugins' => { 'head' => {} }, 'roles' => { 'elastic_role' => {} }, 'scripts' => { 'foo' => { 'source' => 'puppet:///path/to/foo.groovy' } }, 'snapshot_repositories' => { 'backup' => { 'location' => '/backups' } }, 'templates' => { 'foo' => { 'content' => {} } }, 'users' => { 'elastic' => { 'password' => 'foobar' } } }.each_pair do |deftype, params| describe deftype do let(:params) do default_params.merge( deftype => params ) end it { is_expected.to compile } it { expect(subject).to send( "contain_elasticsearch__#{singular(deftype)}", params.keys.first ) } end end end describe 'oss' do let(:params) do default_params.merge(oss: true) end it do expect(subject).to contain_package('elasticsearch').with( name: 'elasticsearch-oss' ) end end end end end diff --git a/spec/defines/003_elasticsearch_template_spec.rb b/spec/defines/003_elasticsearch_template_spec.rb index 27fd8e6..e4f4d67 100644 --- a/spec/defines/003_elasticsearch_template_spec.rb +++ b/spec/defines/003_elasticsearch_template_spec.rb @@ -1,140 +1,138 @@ # frozen_string_literal: true require 'spec_helper' describe 'elasticsearch::template', type: 'define' do on_supported_os( hardwaremodels: ['x86_64'], supported_os: [ { 'operatingsystem' => 'CentOS', 'operatingsystemrelease' => ['6'] } ] ).each do |os, facts| context "on #{os}" do let(:facts) do facts.merge( scenario: '', common: '' ) end let(:title) { 'foo' } let(:pre_condition) do 'class { "elasticsearch" : }' end describe 'parameter validation' do %i[api_ca_file api_ca_path].each do |param| let :params do { :ensure => 'present', :content => '{}', param => 'foo/cert' } end it 'validates cert paths' do expect(subject).to compile.and_raise_error(%r{expects a}) end end describe 'missing parent class' do - let(:pre_condition) {} - it { is_expected.not_to compile } end end describe 'template from source' do let :params do { ensure: 'present', source: 'puppet:///path/to/foo.json', api_protocol: 'https', api_host: '127.0.0.1', api_port: 9201, api_timeout: 11, api_basic_auth_username: 'elastic', api_basic_auth_password: 'password', validate_tls: false } end it { is_expected.to contain_elasticsearch__template('foo') } it do expect(subject).to contain_es_instance_conn_validator('foo-template'). that_comes_before('Elasticsearch_template[foo]') end it 'passes through parameters' do expect(subject).to contain_elasticsearch_template('foo').with( ensure: 'present', source: 'puppet:///path/to/foo.json', protocol: 'https', host: '127.0.0.1', port: 9201, timeout: 11, username: 'elastic', password: 'password', validate_tls: false ) end end describe 'class parameter inheritance' do let :params do { ensure: 'present', content: '{}' } end let(:pre_condition) do <<-EOS class { 'elasticsearch' : api_protocol => 'https', api_host => '127.0.0.1', api_port => 9201, api_timeout => 11, api_basic_auth_username => 'elastic', api_basic_auth_password => 'password', api_ca_file => '/foo/bar.pem', api_ca_path => '/foo/', validate_tls => false, } EOS end it do expect(subject).to contain_elasticsearch_template('foo').with( ensure: 'present', content: '{}', protocol: 'https', host: '127.0.0.1', port: 9201, timeout: 11, username: 'elastic', password: 'password', ca_file: '/foo/bar.pem', ca_path: '/foo/', validate_tls: false ) end end describe 'template deletion' do let :params do { ensure: 'absent' } end it 'removes templates' do expect(subject).to contain_elasticsearch_template('foo').with(ensure: 'absent') end end end end end diff --git a/spec/defines/006_elasticsearch_script_spec.rb b/spec/defines/006_elasticsearch_script_spec.rb index aa5e9f0..bab1541 100644 --- a/spec/defines/006_elasticsearch_script_spec.rb +++ b/spec/defines/006_elasticsearch_script_spec.rb @@ -1,101 +1,99 @@ # frozen_string_literal: true require 'spec_helper' describe 'elasticsearch::script', type: 'define' do let(:title) { 'foo' } let(:pre_condition) do %( class { "elasticsearch": config => { "node" => {"name" => "test" } } } ) end on_supported_os( hardwaremodels: ['x86_64'], supported_os: [ { 'operatingsystem' => 'CentOS', 'operatingsystemrelease' => ['6'] } ] ).each do |os, facts| context "on #{os}" do let(:facts) do facts.merge( scenario: '', common: '' ) end describe 'missing parent class' do - let(:pre_condition) {} - it { is_expected.not_to compile } end describe 'adding script files' do let(:params) do { ensure: 'present', source: 'puppet:///path/to/foo.groovy' } end it { is_expected.to contain_elasticsearch__script('foo') } it { expect(subject).to contain_file('/usr/share/elasticsearch/scripts/foo.groovy'). with( source: 'puppet:///path/to/foo.groovy', ensure: 'present' ) } end describe 'adding script directories' do let(:params) do { ensure: 'directory', source: 'puppet:///path/to/my_scripts', recurse: 'remote' } end it { is_expected.to contain_elasticsearch__script('foo') } it { expect(subject).to contain_file( '/usr/share/elasticsearch/scripts/my_scripts' ).with( ensure: 'directory', source: 'puppet:///path/to/my_scripts', recurse: 'remote' ) } end describe 'removing scripts' do let(:params) do { ensure: 'absent', source: 'puppet:///path/to/foo.groovy' } end it { is_expected.to contain_elasticsearch__script('foo') } it { expect(subject).to contain_file('/usr/share/elasticsearch/scripts/foo.groovy'). with( source: 'puppet:///path/to/foo.groovy', ensure: 'absent' ) } end end end end diff --git a/spec/defines/009_elasticsearch_pipeline_spec.rb b/spec/defines/009_elasticsearch_pipeline_spec.rb index bf9dbe4..9840b61 100644 --- a/spec/defines/009_elasticsearch_pipeline_spec.rb +++ b/spec/defines/009_elasticsearch_pipeline_spec.rb @@ -1,106 +1,104 @@ # frozen_string_literal: true require 'spec_helper' describe 'elasticsearch::pipeline', type: 'define' do let(:title) { 'testpipeline' } let(:pre_condition) do 'class { "elasticsearch" : }' end on_supported_os( hardwaremodels: ['x86_64'], supported_os: [ { 'operatingsystem' => 'CentOS', 'operatingsystemrelease' => ['6'] } ] ).each do |os, facts| context "on #{os}" do let(:facts) do facts.merge( scenario: '', common: '' ) end describe 'parameter validation' do %i[api_ca_file api_ca_path].each do |param| let :params do { :ensure => 'present', :content => {}, param => 'foo/cert' } end it 'validates cert paths' do expect(subject).to compile.and_raise_error(%r{expects a}) end end describe 'missing parent class' do - let(:pre_condition) {} - it { is_expected.not_to compile } end end describe 'class parameter inheritance' do let :params do { ensure: 'present', content: {} } end let(:pre_condition) do <<-EOS class { 'elasticsearch' : api_protocol => 'https', api_host => '127.0.0.1', api_port => 9201, api_timeout => 11, api_basic_auth_username => 'elastic', api_basic_auth_password => 'password', api_ca_file => '/foo/bar.pem', api_ca_path => '/foo/', validate_tls => false, } EOS end it do expect(subject).to contain_elasticsearch__pipeline(title) expect(subject).to contain_es_instance_conn_validator("#{title}-ingest-pipeline"). that_comes_before("elasticsearch_pipeline[#{title}]") expect(subject).to contain_elasticsearch_pipeline(title).with( ensure: 'present', content: {}, protocol: 'https', host: '127.0.0.1', port: 9201, timeout: 11, username: 'elastic', password: 'password', ca_file: '/foo/bar.pem', ca_path: '/foo/', validate_tls: false ) end end describe 'pipeline deletion' do let :params do { ensure: 'absent' } end it 'removes pipelines' do expect(subject).to contain_elasticsearch_pipeline(title).with(ensure: 'absent') end end end end end diff --git a/spec/defines/012_elasticsearch_index_spec.rb b/spec/defines/012_elasticsearch_index_spec.rb index 5e9b38c..d16fb90 100644 --- a/spec/defines/012_elasticsearch_index_spec.rb +++ b/spec/defines/012_elasticsearch_index_spec.rb @@ -1,105 +1,103 @@ # frozen_string_literal: true require 'spec_helper' describe 'elasticsearch::index', type: 'define' do let(:title) { 'test-index' } let(:pre_condition) do 'class { "elasticsearch" : }' end on_supported_os( hardwaremodels: ['x86_64'], supported_os: [ { 'operatingsystem' => 'CentOS', 'operatingsystemrelease' => ['6'] } ] ).each do |os, facts| context "on #{os}" do let(:facts) do facts.merge( scenario: '', common: '' ) end describe 'parameter validation' do %i[api_ca_file api_ca_path].each do |param| let :params do { :ensure => 'present', param => 'foo/cert' } end it 'validates cert paths' do expect(subject).to compile.and_raise_error(%r{expects a}) end end describe 'missing parent class' do - let(:pre_condition) {} - it { is_expected.not_to compile } end end describe 'class parameter inheritance' do let :params do { ensure: 'present' } end let(:pre_condition) do <<-EOS class { 'elasticsearch' : api_protocol => 'https', api_host => '127.0.0.1', api_port => 9201, api_timeout => 11, api_basic_auth_username => 'elastic', api_basic_auth_password => 'password', api_ca_file => '/foo/bar.pem', api_ca_path => '/foo/', validate_tls => false, } EOS end it do expect(subject).to contain_elasticsearch__index(title) expect(subject).to contain_es_instance_conn_validator( "#{title}-index-conn-validator" ).that_comes_before("elasticsearch_index[#{title}]") expect(subject).to contain_elasticsearch_index(title).with( ensure: 'present', settings: {}, protocol: 'https', host: '127.0.0.1', port: 9201, timeout: 11, username: 'elastic', password: 'password', ca_file: '/foo/bar.pem', ca_path: '/foo/', validate_tls: false ) end end describe 'index deletion' do let :params do { ensure: 'absent' } end it 'removes indices' do expect(subject).to contain_elasticsearch_index(title).with(ensure: 'absent') end end end end end diff --git a/spec/defines/013_elasticsearch_snapshot_repository_spec.rb b/spec/defines/013_elasticsearch_snapshot_repository_spec.rb index b9ffc74..a1233be 100644 --- a/spec/defines/013_elasticsearch_snapshot_repository_spec.rb +++ b/spec/defines/013_elasticsearch_snapshot_repository_spec.rb @@ -1,141 +1,139 @@ # frozen_string_literal: true require 'spec_helper' describe 'elasticsearch::snapshot_repository', type: 'define' do on_supported_os( hardwaremodels: ['x86_64'], supported_os: [ { 'operatingsystem' => 'CentOS', 'operatingsystemrelease' => ['6'] } ] ).each do |os, facts| context "on #{os}" do let(:facts) do facts.merge( scenario: '', common: '' ) end let(:title) { 'backup' } let(:pre_condition) do 'class { "elasticsearch" : }' end describe 'parameter validation' do %i[api_ca_file api_ca_path].each do |param| let :params do { :ensure => 'present', :content => '{}', param => 'foo/cert' } end it 'validates cert paths' do expect(subject).to compile.and_raise_error(%r{expects a}) end end describe 'missing parent class' do - let(:pre_condition) {} - it { is_expected.not_to compile } end end describe 'template from source' do let :params do { ensure: 'present', location: '/var/lib/elasticsearch/backup', api_protocol: 'https', api_host: '127.0.0.1', api_port: 9201, api_timeout: 11, api_basic_auth_username: 'elastic', api_basic_auth_password: 'password', validate_tls: false } end it { is_expected.to contain_elasticsearch__snapshot_repository('backup') } it do expect(subject).to contain_es_instance_conn_validator('backup-snapshot'). that_comes_before('Elasticsearch_snapshot_repository[backup]') end it 'passes through parameters' do expect(subject).to contain_elasticsearch_snapshot_repository('backup').with( ensure: 'present', location: '/var/lib/elasticsearch/backup', protocol: 'https', host: '127.0.0.1', port: 9201, timeout: 11, username: 'elastic', password: 'password', validate_tls: false ) end end describe 'class parameter inheritance' do let :params do { ensure: 'present', location: '/var/lib/elasticsearch/backup' } end let(:pre_condition) do <<-MANIFEST class { 'elasticsearch' : api_protocol => 'https', api_host => '127.0.0.1', api_port => 9201, api_timeout => 11, api_basic_auth_username => 'elastic', api_basic_auth_password => 'password', api_ca_file => '/foo/bar.pem', api_ca_path => '/foo/', validate_tls => false, } MANIFEST end it do expect(subject).to contain_elasticsearch_snapshot_repository('backup').with( ensure: 'present', location: '/var/lib/elasticsearch/backup', protocol: 'https', host: '127.0.0.1', port: 9201, timeout: 11, username: 'elastic', password: 'password', ca_file: '/foo/bar.pem', ca_path: '/foo/', validate_tls: false ) end end describe 'snapshot repository deletion' do let :params do { ensure: 'absent', location: '/var/lib/elasticsearch/backup' } end it 'removes snapshot repository' do expect(subject).to contain_elasticsearch_snapshot_repository('backup').with(ensure: 'absent') end end end end end diff --git a/spec/helpers/unit/provider/elasticsearch_rest_shared_examples.rb b/spec/helpers/unit/provider/elasticsearch_rest_shared_examples.rb index 64f4a05..611f2c0 100644 --- a/spec/helpers/unit/provider/elasticsearch_rest_shared_examples.rb +++ b/spec/helpers/unit/provider/elasticsearch_rest_shared_examples.rb @@ -1,108 +1,108 @@ # frozen_string_literal: true require 'json' require 'spec_helper_rspec' require 'webmock/rspec' shared_examples 'REST API' do |resource_type, create_uri, singleton = false| unless singleton describe 'instances' do context "with no #{resource_type}s" do it 'returns an empty list' do stub_request(:get, "http://localhost:9200/_#{resource_type}"). with(headers: { 'Accept' => 'application/json' }). to_return( status: 200, body: '{}' ) expect(described_class.instances).to eq([]) end end end end describe "#{resource_type}s" do if singleton - let(:json) { json_1 } - let(:instance) { [example_1] } + let(:json) { json1 } + let(:instance) { [example1] } else - let(:json) { json_1.merge(json_2) } - let(:instance) { [example_1, example_2] } + let(:json) { json1.merge(json2) } + let(:instance) { [example1, example2] } end it "returns #{resource_type}s" do stub_request(:get, "http://localhost:9200/_#{resource_type}"). with(headers: { 'Accept' => 'application/json' }). to_return( status: 200, body: JSON.dump(json) ) expect(described_class.instances.map do |provider| provider.instance_variable_get(:@property_hash) end).to contain_exactly(*instance) end end describe 'basic authentication' do it 'authenticates' do stub_request(:get, "http://localhost:9200/_#{resource_type}"). with( basic_auth: %w[elastic password], headers: { 'Accept' => 'application/json' } ). to_return( status: 200, - body: JSON.dump(json_1) + body: JSON.dump(json1) ) expect(described_class.api_objects( - 'http', true, 'localhost', '9200', 10, 'elastic', 'password' + 'http', 'localhost', '9200', 10, 'elastic', 'password', validate_tls: true ).map do |provider| described_class.new( provider ).instance_variable_get(:@property_hash) - end).to contain_exactly(example_1) + end).to contain_exactly(example1) end end describe 'https' do it 'uses ssl' do stub_request(:get, "https://localhost:9200/_#{resource_type}"). with(headers: { 'Accept' => 'application/json' }). to_return( status: 200, - body: JSON.dump(json_1) + body: JSON.dump(json1) ) expect(described_class.api_objects( - 'https', true, 'localhost', '9200', 10 + 'https', 'localhost', '9200', 10, validate_tls: true ).map do |provider| described_class.new( provider ).instance_variable_get(:@property_hash) - end).to contain_exactly(example_1) + end).to contain_exactly(example1) end end unless singleton describe 'flush' do it "creates #{resource_type}s" do stub_request(:put, "http://localhost:9200/#{create_uri}"). with( headers: { 'Accept' => 'application/json', 'Content-Type' => 'application/json' }, body: bare_resource ) stub_request(:get, "http://localhost:9200/_#{resource_type}"). with(headers: { 'Accept' => 'application/json' }). to_return(status: 200, body: '{}') provider.flush end end end end diff --git a/spec/spec_utilities.rb b/spec/spec_utilities.rb index 2782028..daaeb76 100644 --- a/spec/spec_utilities.rb +++ b/spec/spec_utilities.rb @@ -1,136 +1,136 @@ # frozen_string_literal: true require 'bcrypt' require 'open-uri' def to_agent_version(puppet_version) # REF: https://docs.puppet.com/puppet/latest/reference/about_agent.html { # Puppet => Agent '4.10.4' => '1.10.4', '4.10.3' => '1.10.3', '4.10.2' => '1.10.2', '4.10.1' => '1.10.1', '4.10.0' => '1.10.0', '4.9.4' => '1.9.3', '4.8.2' => '1.8.3', '4.7.1' => '1.7.2', '4.7.0' => '1.7.1', '4.6.2' => '1.6.2', '4.5.3' => '1.5.3', '4.4.2' => '1.4.2', '4.4.1' => '1.4.1', '4.4.0' => '1.4.0', '4.3.2' => '1.3.6', '4.3.1' => '1.3.2', '4.3.0' => '1.3.0', '4.2.3' => '1.2.7', '4.2.2' => '1.2.6', '4.2.1' => '1.2.2', '4.2.0' => '1.2.1', '4.1.0' => '1.1.1', '4.0.0' => '1.0.1' }[puppet_version] end def derive_artifact_urls_for(full_version, plugins = ['analysis-icu']) derive_full_package_url(full_version).merge( derive_plugin_urls_for(full_version, plugins) ) end def derive_full_package_url(full_version, extensions = %w[deb rpm]) extensions.map do |ext| url = if full_version.start_with? '6' "https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-#{full_version}.#{ext}" elsif ext == 'deb' "https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-#{full_version}-amd64.#{ext}" else "https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-#{full_version}-x86_64.#{ext}" end [url, File.basename(url)] end.to_h end def derive_plugin_urls_for(full_version, plugins = ['analysis-icu']) plugins.map do |plugin| url = "https://artifacts.elastic.co/downloads/elasticsearch-plugins/#{plugin}/#{plugin}-#{full_version}.zip" [url, File.join('plugins', File.basename(url))] end.to_h end def artifact(file, fixture_path = []) File.join(%w[spec fixtures artifacts] + fixture_path + [File.basename(file)]) end def get(url, file_path) puts "Fetching #{url}..." found = false until found uri = URI.parse(url) conn = Net::HTTP.new(uri.host, uri.port) conn.use_ssl = true res = conn.get(uri.path) if res.header['location'] url = res.header['location'] else found = true end end File.open(file_path, 'w+') { |fh| fh.write res.body } end def fetch_archives(archives) archives.each do |url, orig_fp| fp = "spec/fixtures/artifacts/#{orig_fp}" if File.exist? fp if fp.end_with?('tar.gz') && !system("tar -tzf #{fp} &>/dev/null") puts "Archive #{fp} corrupt, re-fetching..." File.delete fp else puts "Already retrieved intact archive #{fp}..." next end end get url, fp end end def pid_file if fact('operatingsystem') == 'Debian' \ && fact('lsbmajdistrelease').to_i <= 7 '/var/run/elasticsearch.pid' else '/var/run/elasticsearch/elasticsearch.pid' end end def vault_available? if ENV['CI'] %w[VAULT_ADDR VAULT_APPROLE_ROLE_ID VAULT_APPROLE_SECRET_ID VAULT_PATH].select do |var| ENV[var].nil? end.empty? else true end end def http_retry(url) retries ||= 0 - open(url).read + URI.parse(url).open.read rescue StandardError retry if (retries += 1) < 3 end # Helper to store arbitrary testing setting values def v RSpec.configuration.v end def semver(version) Gem::Version.new version end def bcrypt(value) BCrypt::Password.create(value) end diff --git a/spec/unit/provider/elasticsearch_index/ruby_spec.rb b/spec/unit/provider/elasticsearch_index/ruby_spec.rb index 05ffa8a..dfccc7a 100644 --- a/spec/unit/provider/elasticsearch_index/ruby_spec.rb +++ b/spec/unit/provider/elasticsearch_index/ruby_spec.rb @@ -1,128 +1,128 @@ # frozen_string_literal: true require_relative '../../../helpers/unit/provider/elasticsearch_rest_shared_examples' -describe Puppet::Type.type(:elasticsearch_index).provider(:ruby) do +describe Puppet::Type.type(:elasticsearch_index).provider(:ruby) do # rubocop:disable RSpec/MultipleMemoizedHelpers let(:name) { 'test-index' } - let(:example_1) do + let(:example1) do { name: 'index-one', ensure: :present, provider: :ruby, settings: { 'index' => { 'creation_date' => 1_487_354_196_301, 'number_of_replicas' => 1, 'number_of_shards' => 5, 'provided_name' => 'a', 'routing' => { 'allocation' => { 'include' => { 'size' => 'big' } } }, 'store' => { 'type' => 'niofs' }, 'uuid' => 'vtJrcgyeRviqllRakSlrSw', 'version' => { 'created' => 5_020_199 } } } } end - let(:json_1) do + let(:json1) do { 'index-one' => { 'settings' => { 'index' => { 'creation_date' => '1487354196301', 'number_of_replicas' => '1', 'number_of_shards' => '5', 'provided_name' => 'a', 'routing' => { 'allocation' => { 'include' => { 'size' => 'big' } } }, 'store' => { 'type' => 'niofs' }, 'uuid' => 'vtJrcgyeRviqllRakSlrSw', 'version' => { 'created' => '5020199' } } } } } end - let(:example_2) do + let(:example2) do { name: 'index-two', ensure: :present, provider: :ruby, settings: { 'index' => { 'creation_date' => 1_487_354_196_301, 'number_of_replicas' => 1, 'number_of_shards' => 5, 'provided_name' => 'a', 'uuid' => 'vtJrcgyeRviqllRakSlrSw', 'version' => { 'created' => 5_020_199 } } } } end - let(:json_2) do + let(:json2) do { 'index-two' => { 'settings' => { 'index' => { 'creation_date' => '1487354196301', 'number_of_replicas' => '1', 'number_of_shards' => '5', 'provided_name' => 'a', 'uuid' => 'vtJrcgyeRviqllRakSlrSw', 'version' => { 'created' => '5020199' } } } } } end let(:bare_resource) do JSON.dump( 'index' => { 'number_of_replicas' => 0 } ) end let(:resource) { Puppet::Type::Elasticsearch_index.new props } let(:provider) { described_class.new resource } let(:props) do { name: name, settings: { 'index' => { 'number_of_replicas' => '0' } } } end include_examples 'REST API', 'all/_settings', 'test-index/_settings' end diff --git a/spec/unit/provider/elasticsearch_license/xpack_spec.rb b/spec/unit/provider/elasticsearch_license/xpack_spec.rb index e94e32b..00691ac 100644 --- a/spec/unit/provider/elasticsearch_license/xpack_spec.rb +++ b/spec/unit/provider/elasticsearch_license/xpack_spec.rb @@ -1,63 +1,63 @@ # frozen_string_literal: true require_relative '../../../helpers/unit/provider/elasticsearch_rest_shared_examples' -describe Puppet::Type.type(:elasticsearch_license).provider(:xpack) do +describe Puppet::Type.type(:elasticsearch_license).provider(:xpack) do # rubocop:disable RSpec/MultipleMemoizedHelpers let(:name) { 'xpack' } - let(:example_1) do + let(:example1) do { name: 'xpack', ensure: :present, provider: :xpack, content: { 'license' => { 'status' => 'active', 'uid' => 'cbff45e7-c553-41f7-ae4f-9205eabd80xx', 'type' => 'trial', 'issue_date' => '2018-02-22T23:12:05.550Z', 'issue_date_in_millis' => 1_519_341_125_550, 'expiry_date' => '2018-03-24T23:12:05.550Z', 'expiry_date_in_millis' => 1_521_933_125_550, 'max_nodes' => 1_000, 'issued_to' => 'test', 'issuer' => 'elasticsearch', 'start_date_in_millis' => 1_513_814_400_000 } } } end - let(:json_1) do + let(:json1) do { 'license' => { 'status' => 'active', 'uid' => 'cbff45e7-c553-41f7-ae4f-9205eabd80xx', 'type' => 'trial', 'issue_date' => '2018-02-22T23:12:05.550Z', 'issue_date_in_millis' => '1519341125550', 'expiry_date' => '2018-03-24T23:12:05.550Z', 'expiry_date_in_millis' => '1521933125550', 'max_nodes' => '1000', 'issued_to' => 'test', 'issuer' => 'elasticsearch', 'start_date_in_millis' => '1513814400000' } } end let(:resource) { Puppet::Type::Elasticsearch_index.new props } let(:provider) { described_class.new resource } let(:props) do { name: name, settings: { 'index' => { 'number_of_replicas' => 0 } } } end include_examples 'REST API', 'xpack/license', nil, true end diff --git a/spec/unit/provider/elasticsearch_pipeline/ruby_spec.rb b/spec/unit/provider/elasticsearch_pipeline/ruby_spec.rb index 2619796..9666f9a 100644 --- a/spec/unit/provider/elasticsearch_pipeline/ruby_spec.rb +++ b/spec/unit/provider/elasticsearch_pipeline/ruby_spec.rb @@ -1,98 +1,98 @@ # frozen_string_literal: true require_relative '../../../helpers/unit/provider/elasticsearch_rest_shared_examples' -describe Puppet::Type.type(:elasticsearch_pipeline).provider(:ruby) do - let(:example_1) do +describe Puppet::Type.type(:elasticsearch_pipeline).provider(:ruby) do # rubocop:disable RSpec/MultipleMemoizedHelpers + let(:example1) do { name: 'foo', ensure: :present, provider: :ruby, content: { 'description' => 'Sets the foo field to "bar"', 'processors' => [{ 'set' => { 'field' => 'foo', 'value' => 'bar' } }] } } end - let(:json_1) do + let(:json1) do { 'foo' => { 'description' => 'Sets the foo field to "bar"', 'processors' => [{ 'set' => { 'field' => 'foo', 'value' => 'bar' } }] } } end - let(:example_2) do + let(:example2) do { name: 'baz', ensure: :present, provider: :ruby, content: { 'description' => 'A pipeline that never gives you up', 'processors' => [{ 'set' => { 'field' => 'firstname', 'value' => 'rick' } }, { 'set' => { 'field' => 'lastname', 'value' => 'astley' } }] } } end - let(:json_2) do + let(:json2) do { 'baz' => { 'description' => 'A pipeline that never gives you up', 'processors' => [{ 'set' => { 'field' => 'firstname', 'value' => 'rick' } }, { 'set' => { 'field' => 'lastname', 'value' => 'astley' } }] } } end let(:bare_resource) do JSON.dump( 'description' => 'Empty pipeline', 'processors' => [] ) end let(:resource) { Puppet::Type::Elasticsearch_pipeline.new props } let(:provider) { described_class.new resource } let(:props) do { name: 'foo', content: { 'description' => 'Empty pipeline', 'processors' => [] } } end include_examples 'REST API', 'ingest/pipeline', '_ingest/pipeline/foo' end diff --git a/spec/unit/provider/elasticsearch_plugin/shared_examples.rb b/spec/unit/provider/elasticsearch_plugin/shared_examples.rb index eaebe4f..6053860 100644 --- a/spec/unit/provider/elasticsearch_plugin/shared_examples.rb +++ b/spec/unit/provider/elasticsearch_plugin/shared_examples.rb @@ -1,195 +1,187 @@ # frozen_string_literal: true require 'spec_helper_rspec' shared_examples 'plugin provider' do |version| describe "elasticsearch #{version}" do before do allow(File).to receive(:open) allow(provider).to receive(:es_version).and_return version end describe 'setup' do it 'installs with default parameters' do allow(provider).to receive(:plugin).with( ['install', resource_name].tap do |args| args.insert 1, '--batch' if Puppet::Util::Package.versioncmp(version, '2.2.0') >= 0 end ) provider.create expect(provider).to have_received(:plugin).with( ['install', resource_name].tap do |args| args.insert 1, '--batch' if Puppet::Util::Package.versioncmp(version, '2.2.0') >= 0 end ) end it 'installs via URLs' do resource[:url] = 'http://url/to/my/plugin.zip' allow(provider).to receive(:plugin).with( ['install'] + ['http://url/to/my/plugin.zip'].tap do |args| args.unshift('kopf', '--url') if version.start_with? '1' args.unshift '--batch' if Puppet::Util::Package.versioncmp(version, '2.2.0') >= 0 - - args end ) provider.create expect(provider).to have_received(:plugin).with( ['install'] + ['http://url/to/my/plugin.zip'].tap do |args| args.unshift('kopf', '--url') if version.start_with? '1' args.unshift '--batch' if Puppet::Util::Package.versioncmp(version, '2.2.0') >= 0 - - args end ) end it 'installs with a local file' do resource[:source] = '/tmp/plugin.zip' allow(provider).to receive(:plugin).with( ['install'] + ['file:///tmp/plugin.zip'].tap do |args| args.unshift('kopf', '--url') if version.start_with? '1' args.unshift '--batch' if Puppet::Util::Package.versioncmp(version, '2.2.0') >= 0 - - args end ) provider.create expect(provider).to have_received(:plugin).with( ['install'] + ['file:///tmp/plugin.zip'].tap do |args| args.unshift('kopf', '--url') if version.start_with? '1' args.unshift '--batch' if Puppet::Util::Package.versioncmp(version, '2.2.0') >= 0 - - args end ) end describe 'proxying' do it 'installs behind a proxy' do resource[:proxy] = 'http://localhost:3128' allow(provider). to receive(:plugin). with([ '-Dhttp.proxyHost=localhost', '-Dhttp.proxyPort=3128', '-Dhttps.proxyHost=localhost', '-Dhttps.proxyPort=3128', 'install', '--batch', resource_name ]) provider.create expect(provider). to have_received(:plugin). with([ '-Dhttp.proxyHost=localhost', '-Dhttp.proxyPort=3128', '-Dhttps.proxyHost=localhost', '-Dhttps.proxyPort=3128', 'install', '--batch', resource_name ]) end it 'uses authentication credentials' do resource[:proxy] = 'http://elastic:password@es.local:8080' allow(provider). to receive(:plugin). with([ '-Dhttp.proxyHost=es.local', '-Dhttp.proxyPort=8080', '-Dhttp.proxyUser=elastic', '-Dhttp.proxyPassword=password', '-Dhttps.proxyHost=es.local', '-Dhttps.proxyPort=8080', '-Dhttps.proxyUser=elastic', '-Dhttps.proxyPassword=password', 'install', '--batch', resource_name ]) provider.create expect(provider). to have_received(:plugin). with([ '-Dhttp.proxyHost=es.local', '-Dhttp.proxyPort=8080', '-Dhttp.proxyUser=elastic', '-Dhttp.proxyPassword=password', '-Dhttps.proxyHost=es.local', '-Dhttps.proxyPort=8080', '-Dhttps.proxyUser=elastic', '-Dhttps.proxyPassword=password', 'install', '--batch', resource_name ]) end end describe 'configdir' do it 'sets the ES_PATH_CONF env var' do resource[:configdir] = '/etc/elasticsearch' expect(provider.with_environment do ENV['ES_PATH_CONF'] end).to eq('/etc/elasticsearch') end end end describe 'java_opts' do it 'uses authentication credentials' do resource[:java_opts] = ['-Des.plugins.staging=4a2ffaf5'] expect(provider.with_environment do ENV['ES_JAVA_OPTS'] end).to eq('-Des.plugins.staging=4a2ffaf5') end end describe 'java_home' do it 'sets the JAVA_HOME env var' do resource[:java_home] = '/opt/foo' expect(provider.with_environment do ENV['JAVA_HOME'] end).to eq('/opt/foo') end end describe 'java_home unset' do elasticsearch_java_home = '/usr/share/elasticsearch/jdk' it 'defaults to the elasticsearch bundled JDK' do resource[:java_home] = '' expect(provider.with_environment do ENV['JAVA_HOME'] end).to eq(elasticsearch_java_home) end end describe 'plugin_name' do let(:resource_name) { 'appbaseio/dejaVu' } it 'maintains mixed-case names' do expect(provider.plugin_path).to include('dejaVu') end end describe 'removal' do it 'uninstalls the plugin' do allow(provider).to receive(:plugin).with( ['remove', resource_name.split('-').last] ) provider.destroy expect(provider).to have_received(:plugin).with( ['remove', resource_name.split('-').last] ) end end end end diff --git a/spec/unit/provider/elasticsearch_snapshot_repository/ruby_spec.rb b/spec/unit/provider/elasticsearch_snapshot_repository/ruby_spec.rb index 4531da1..684a1e0 100644 --- a/spec/unit/provider/elasticsearch_snapshot_repository/ruby_spec.rb +++ b/spec/unit/provider/elasticsearch_snapshot_repository/ruby_spec.rb @@ -1,74 +1,74 @@ # frozen_string_literal: true require_relative '../../../helpers/unit/provider/elasticsearch_rest_shared_examples' -describe Puppet::Type.type(:elasticsearch_snapshot_repository).provider(:ruby) do - let(:example_1) do +describe Puppet::Type.type(:elasticsearch_snapshot_repository).provider(:ruby) do # rubocop:disable RSpec/MultipleMemoizedHelpers + let(:example1) do { name: 'foobar1', ensure: :present, provider: :ruby, location: '/bak1', type: 'fs', compress: true } end - let(:json_1) do + let(:json1) do { 'foobar1' => { 'type' => 'fs', 'settings' => { 'compress' => true, 'location' => '/bak1' } } } end - let(:example_2) do + let(:example2) do { name: 'foobar2', ensure: :present, provider: :ruby, location: '/bak2', type: 'fs', compress: true } end - let(:json_2) do + let(:json2) do { 'foobar2' => { 'type' => 'fs', 'settings' => { 'compress' => true, 'location' => '/bak2' } } } end let(:bare_resource) do JSON.dump( 'type' => 'fs', 'settings' => { 'compress' => true, 'location' => '/backups' } ) end let(:resource) { Puppet::Type::Elasticsearch_snapshot_repository.new props } let(:provider) { described_class.new resource } let(:props) do { name: 'backup', type: 'fs', compress: true, location: '/backups' } end include_examples 'REST API', 'snapshot', '_snapshot/backup' end diff --git a/spec/unit/provider/elasticsearch_template/ruby_spec.rb b/spec/unit/provider/elasticsearch_template/ruby_spec.rb index cc5f86d..848f57f 100644 --- a/spec/unit/provider/elasticsearch_template/ruby_spec.rb +++ b/spec/unit/provider/elasticsearch_template/ruby_spec.rb @@ -1,81 +1,81 @@ # frozen_string_literal: true require_relative '../../../helpers/unit/provider/elasticsearch_rest_shared_examples' -describe Puppet::Type.type(:elasticsearch_template).provider(:ruby) do - let(:example_1) do +describe Puppet::Type.type(:elasticsearch_template).provider(:ruby) do # rubocop:disable RSpec/MultipleMemoizedHelpers + let(:example1) do { name: 'foobar1', ensure: :present, provider: :ruby, content: { 'aliases' => {}, 'mappings' => {}, 'settings' => {}, 'template' => 'foobar1-*', 'order' => 1 } } end - let(:json_1) do + let(:json1) do { 'foobar1' => { 'aliases' => {}, 'mappings' => {}, 'order' => 1, 'settings' => {}, 'template' => 'foobar1-*' } } end - let(:example_2) do + let(:example2) do { name: 'foobar2', ensure: :present, provider: :ruby, content: { 'aliases' => {}, 'mappings' => {}, 'settings' => {}, 'template' => 'foobar2-*', 'order' => 2 } } end - let(:json_2) do + let(:json2) do { 'foobar2' => { 'aliases' => {}, 'mappings' => {}, 'order' => 2, 'settings' => {}, 'template' => 'foobar2-*' } } end let(:bare_resource) do JSON.dump( 'order' => 0, 'aliases' => {}, 'mappings' => {}, 'template' => 'fooindex-*' ) end let(:resource) { Puppet::Type::Elasticsearch_template.new props } let(:provider) { described_class.new resource } let(:props) do { name: 'foo', content: { 'template' => 'fooindex-*' } } end include_examples 'REST API', 'template', '_template/foo' end