diff --git a/.gitignore b/.gitignore index e43b0f988..1a7b3a706 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,23 @@ -.DS_Store +*~ +*# +.#* +\#*# +.*.sw[a-z] +*.un~ +pkg/ + +# Berkshelf +.vagrant +/cookbooks +Berksfile.lock + +# Bundler +Gemfile.lock +bin/* +.bundle/* + +.kitchen/ +.kitchen.local.yml + +# chefspec +.coverage/ diff --git a/.kitchen.yml b/.kitchen.yml new file mode 100644 index 000000000..0eabad95d --- /dev/null +++ b/.kitchen.yml @@ -0,0 +1,41 @@ +--- +################################################################################ +# Any local customizations should be placed inside the .kitchen.local.yml, which +# will not be checked in or overwritten. You may also use ~/.kitchen/config.yml +# or environment variables like VAGRANT_DEFAULT_PROVIDER. Anything in those will +# take precedence over anything set in `.kitchen.yml`. +################################################################################ +driver: + name: vagrant # provide a default test-kitchen driver, vagrant + +driver_config: + require_chef_omnibus: latest + +provisioner: + name: chef_zero + nodes_path: 'test/fixtures/nodes' + data_bags_path: 'test/fixtures/data_bags' + environments_path: 'test/fixtures/environments' + # commented as it doesn't currently exist + # encrypted_data_bag_secret_key_path: "test/fixtures/encrypted_data_bag_secret_for_test" + client_rb: + environment: _default + attributes: + foo: 'bar' + +platforms: + - name: ubuntu-14.04 + run_list: + - recipe[apt] + - name: ubuntu-12.04 + run_list: + - recipe[apt] + - name: centos-6.5 + run_list: + - recipe[yum] + +suites: + - name: default + run_list: recipe[elasticsearch] + attributes: + foo: 'bar' diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 000000000..a6994e70e --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,45 @@ +# Rubocop, we're buddies and all, but we're going to have to disagree on the following - + +# Disable requirement of "encoding" headers on files +Encoding: + Enabled: false + +# Increase line length, we're not on VT220s anymore +LineLength: + Max: 180 + +# Increase allowed lines in a method. Short methods are good, but 10 lines +# is a bit too low. +MethodLength: + Max: 40 + +# Favor explicit over implicit code: don't complain of "redundant returns" +RedundantReturn: + Enabled: false + +# Don't complain about if/unless modifiers. The merit of this is debatable +# and it will likely require building of over-length lines. +IfUnlessModifier: + Enabled: false + +# Raise allowed CyclomaticComplexity to 10. +CyclomaticComplexity: + Max: 10 + +# Disable Single Space before first arg +SingleSpaceBeforeFirstArg: + Enabled: false + +# Don't force a word array unless 5 elements +WordArray: + MinSize: 5 + +# Don't complain about unused block args +UnusedBlockArgument: + Enabled: false + +# There are too many non-ruby files that run up against rubocop rules in a cookbook +AllCops: + Include: + - '**/metadata.rb' + - '**/*.rb' diff --git a/Berksfile b/Berksfile new file mode 100644 index 000000000..c2b41f6c5 --- /dev/null +++ b/Berksfile @@ -0,0 +1,7 @@ +source "https://supermarket.getchef.com" + +metadata + +group :integration do + cookbook "elasticsearch_test", :path => "./test/fixtures/cookbooks/elasticsearch_test" +end diff --git a/Gemfile b/Gemfile new file mode 100644 index 000000000..59d46c995 --- /dev/null +++ b/Gemfile @@ -0,0 +1,40 @@ +source 'https://rubygems.org' + +group :lint do + gem 'foodcritic', '~> 3.0' + gem 'rubocop', '~> 0.26' +end + +group :unit do + gem 'berkshelf', '~> 3' + gem 'chefspec' + gem 'chef-sugar' +end + +group :kitchen_common do + gem 'test-kitchen' +end + +group :kitchen_vagrant do + gem 'kitchen-vagrant' + gem 'vagrant-wrapper' +end + +group :kitchen_rackspace do + gem 'kitchen-rackspace' +end + +group :kitchen_ec2 do + gem 'kitchen-ec2' +end + +group :development do + gem 'growl' + gem 'rb-fsevent' + gem 'guard' + gem 'guard-kitchen' + gem 'guard-foodcritic' + gem 'guard-rubocop' + gem 'fauxhai' + gem 'pry-nav' +end diff --git a/README.markdown b/README.markdown deleted file mode 100644 index 8dc305acc..000000000 --- a/README.markdown +++ /dev/null @@ -1,21 +0,0 @@ -# Elasticsearch Chef Cookbook - -This branch contains the next version of the cookbook. - -## License - -This software is licensed under the Apache 2 license, quoted below. - - Copyright (c) 2014 Elasticsearch - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 000000000..a660f865b --- /dev/null +++ b/README.md @@ -0,0 +1,94 @@ +# Elasticsearch Chef Cookbook + +This branch contains the next version of the cookbook. + +## Testing + +This cookbook is equipped with both unit tests (chefspec) and integration tests +(test-kitchen and serverspec). It also comes with rubocop and foodcritic tasks +in the supplied Rakefile. Contributions to this cookbook should include tests +for new features or bugfixes, with a preference for unit tests over integration +tests to ensure speedy testing runs. ***All tests and most other commands here +should be run using bundler*** and our standard Gemfile. This ensures that +contributions and changes are made in a standardized way against the same +versions of gems. We recommend installing rubygems-bundler so that bundler is +automatically inserting `bundle exec` in front of commands run in a directory +that contains a Gemfile. + +A full test run of all tests and style checks would look like: +```bash +$ bundle exec rake style +$ bundle exec rake spec +$ bundle exec rake integration +$ bundle exec rake destroy +``` +The final destroy is intended to clean up any systems that failed a test, and is +mostly useful when running with kitchen drivers for cloud providers, so that no +machines are left orphaned and costing you money. + +### Fixtures + +This cookbook supplies a few different test fixtures (under `test/fixtures/`) +that can be shared amongst any number of unit or integration tests: cookbooks, +environments, and nodes. Environments and nodes are automatically loaded into +chef-zero for both chefspec tests that run locally and serverspec tests that run +from test-kitchen. + +It also contains 'platform data' that can be used to drive unit testing, for +example, you might read `httpd` for some platforms and `apache2` for others, +allowing you to write a single test for the Apache webserver. Unfortunately, +without further modifications to `busser` and `busser-serverspec`, the platform +data will not be available to serverspec tests. + +### Style and Best Practices + +Rubocop and Foodcritic evaluations may be made by running `rake style`. There +are no overrides for foodcritic rules, however the adjustments to +rubocop are made using the supplied `.rubocop.yml` file and have been documented +by comments within. Most notably, rubocop has been restricted to only apply to +`.rb` files. + +Rubocop and foodcritic tests can be executed using `rake style`. + +### Unit testing + +Unit testing is done using the latest versions of Chefspec. The current default +test layout includes running against all supported platforms, as well as +stubbing data into chef-zero. This allows us to also test against chef search. +As is currently a best practice in the community, we will avoid the use of +chef-solo, but not create barriers to explicitly fail for chef-solo. + +Unit tests can be executed using `rake spec`. + +### Integration testing + +Integration testing is accomplished using the latest versions of test-kitchen +and serverspec. Currently, this cookbook uses the busser-serverspec plugin for +copying serverspec files to the system being tested. There is some debate in the +community about whether this should be done using busser-rspec instead, and each +busser plugin has a slightly different feature set. + +While the default test-kitchen configuration uses the vagrant driver, you may +override this using `~/.kitchen/config.yml` or by placing a `.kitchen.local.yml` +in the current directory. This allows you to run these integration tests using +any supported test-kitchen driver (ec2, rackspace, docker, etc). + +Integration tests can be executed using `rake integration` or `kitchen test`. + +## License + +This software is licensed under the Apache 2 license, quoted below. + + Copyright (c) 2014 Elasticsearch + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Rakefile b/Rakefile new file mode 100644 index 000000000..5b6f27972 --- /dev/null +++ b/Rakefile @@ -0,0 +1,63 @@ +# Encoding: utf-8 +require 'bundler/setup' + +namespace :style do + require 'rubocop/rake_task' + desc 'Run Ruby style checks' + RuboCop::RakeTask.new(:ruby) do |task| + # see templatestack's .rubocop.yml for comparison + task.patterns = ['**/*.rb'] + + # only show the files with failures + task.formatters = ['files'] + + # abort rake on failure + task.fail_on_error = true + end + + require 'foodcritic' + desc 'Run Chef style checks' + FoodCritic::Rake::LintTask.new(:chef) do |t| + # 'search_gems' doesn't work, but :search_gems does + # rubocop:disable Style/HashSyntax + t.options = { :search_gems => true, # allows us to add addl gems with more rules + :fail_tags => ['correctness'], + :chef_version => '11.6.0' + } + # rubocop:enable Style/HashSyntax + end +end + +desc 'Run all style checks' +task style: ['style:chef', 'style:ruby'] + +require 'kitchen' +desc 'Run Test Kitchen integration tests' +task :integration do + Kitchen.logger = Kitchen.default_file_logger + sh 'kitchen test -c' +end + +desc 'Destroy test kitchen instances' +task :destroy do + Kitchen.logger = Kitchen.default_file_logger + Kitchen::Config.new.instances.each do |instance| + instance.destroy + end +end + +require 'rspec/core/rake_task' +desc 'Run ChefSpec unit tests' +RSpec::Core::RakeTask.new(:spec) do |t, args| + t.rspec_opts = 'test/unit' +end + +# The default rake task should just run it all +task default: ['style', 'spec', 'integration'] + +begin + require 'kitchen/rake_tasks' + Kitchen::RakeTasks.new + rescue LoadError + puts '>>>>> Kitchen gem not loaded, omitting tasks' unless ENV['CI'] +end diff --git a/metadata.rb b/metadata.rb new file mode 100644 index 000000000..35dc2e2e5 --- /dev/null +++ b/metadata.rb @@ -0,0 +1,13 @@ +# Encoding: utf-8 +name 'elasticsearch' +maintainer 'karmi' +maintainer_email 'karmi@karmi.cz' +license 'Apache 2.0' +description 'Installs/Configures elasticsearch' +long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) +version '1.0.0' + +depends 'apt' +depends 'yum' +depends 'chef-sugar' +depends 'curl' diff --git a/recipes/default.rb b/recipes/default.rb new file mode 100644 index 000000000..dfbe41dd3 --- /dev/null +++ b/recipes/default.rb @@ -0,0 +1,15 @@ +# Encoding: utf-8 +# +# Cookbook Name:: elasticsearch +# Recipe:: default +# + +include_recipe 'chef-sugar' +include_recipe 'curl' + +ruby_block 'dummy_block for test coverage' do + block do + # some Ruby code + end + action :run +end diff --git a/test/fixtures/cookbooks/elasticsearch_test/README.md b/test/fixtures/cookbooks/elasticsearch_test/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/test/fixtures/cookbooks/elasticsearch_test/metadata.rb b/test/fixtures/cookbooks/elasticsearch_test/metadata.rb new file mode 100644 index 000000000..271b8d662 --- /dev/null +++ b/test/fixtures/cookbooks/elasticsearch_test/metadata.rb @@ -0,0 +1,12 @@ +# Encoding: utf-8 +name 'elasticsearch_test' +maintainer 'karmi' +maintainer_email 'karmi@karmi.cz' +license 'Apache 2.0' +description 'A wrapper cookbook for use in testing that elasticsearch cookbook works well with wrappers calling it' +long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) +version '0.1.0' + +depends 'apt' +depends 'yum' +depends 'chef-sugar' diff --git a/test/fixtures/cookbooks/elasticsearch_test/recipes/default.rb b/test/fixtures/cookbooks/elasticsearch_test/recipes/default.rb new file mode 100644 index 000000000..581e9f680 --- /dev/null +++ b/test/fixtures/cookbooks/elasticsearch_test/recipes/default.rb @@ -0,0 +1,4 @@ +# this is a test fixture used to test that the elasticsearch cookbook's +# provided LWRPs and recipes can be used correctly from a wrapper + +include_recipe 'chef-sugar' # placeholder while we're writing recipes still diff --git a/test/fixtures/environments/chefspec.json b/test/fixtures/environments/chefspec.json new file mode 100644 index 000000000..62d3e6349 --- /dev/null +++ b/test/fixtures/environments/chefspec.json @@ -0,0 +1,12 @@ +{ + "name": "chefspec", + "description": "Stubbed Environment for Chefspec", + "cookbook_versions": { + }, + "json_class": "Chef::Environment", + "chef_type": "environment", + "default_attributes": { + }, + "override_attributes": { + } +} diff --git a/test/fixtures/nodes/node01.json b/test/fixtures/nodes/node01.json new file mode 100644 index 000000000..6b742019a --- /dev/null +++ b/test/fixtures/nodes/node01.json @@ -0,0 +1,27 @@ +{ + "name": "node01", + "chef_environment": "_default", + "json_class": "Chef::Node", + "automatic": { + "hostname": "vagrant.vm", + "recipes": [ + "elasticsearch::default" + ], + "roles": [ + ], + "ipaddress": "192.168.0.1" + }, + "tags": [ + "app" + ], + "normal": { + }, + "chef_type": "node", + "default": { + }, + "override": { + }, + "run_list": [ + "recipe[elasticsearch::default]" + ] +} diff --git a/test/fixtures/platform/ubuntu/12.04.json b/test/fixtures/platform/ubuntu/12.04.json new file mode 100644 index 000000000..eceb2aca4 --- /dev/null +++ b/test/fixtures/platform/ubuntu/12.04.json @@ -0,0 +1,10 @@ +{ + "platform": "ubuntu", + "platform_family": "debian", + "platform_version": "12.04", + "version": "12.04", + "lsb": { + "codename": "lucid" + }, + "curl_package" : "curl" +} diff --git a/test/integration/default/serverspec/default_spec.rb b/test/integration/default/serverspec/default_spec.rb new file mode 100644 index 000000000..d5ca99991 --- /dev/null +++ b/test/integration/default/serverspec/default_spec.rb @@ -0,0 +1,8 @@ +# Encoding: utf-8 + +require_relative 'spec_helper' + +describe command('which curl') do + its(:stdout) { should match(/curl/) } + its(:exit_status) { should eq 0 } +end diff --git a/test/integration/default/serverspec/spec_helper.rb b/test/integration/default/serverspec/spec_helper.rb new file mode 100644 index 000000000..c4a0121b8 --- /dev/null +++ b/test/integration/default/serverspec/spec_helper.rb @@ -0,0 +1,5 @@ +# Encoding: utf-8 +require 'serverspec' + +set :backend, :exec +set :path, '/sbin:/usr/local/sbin:$PATH' diff --git a/test/unit/spec/default_spec.rb b/test/unit/spec/default_spec.rb new file mode 100644 index 000000000..fb4cdfa7e --- /dev/null +++ b/test/unit/spec/default_spec.rb @@ -0,0 +1,39 @@ +# Encoding: utf-8 + +require_relative 'spec_helper' + +describe 'elasticsearch::default' 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) 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) + + # added as an example, but this probably isn't a great one, since we shouldn't be + # testing resources that are not created/executed by our cookbook. + it 'upgrades curl' do + + # example of using a platform specific property to override a package name + curl_package_name = property['curl_package'] || 'curl' + + # ensure package is installed (action is :upgrade) + expect(chef_run).to upgrade_package(curl_package_name) + + end + + it 'creates a dummy ruby block for test coverage' do + expect(chef_run).to run_ruby_block('dummy_block for test coverage') + end + + end + end + end +end diff --git a/test/unit/spec/spec_helper.rb b/test/unit/spec/spec_helper.rb new file mode 100644 index 000000000..84628c096 --- /dev/null +++ b/test/unit/spec/spec_helper.rb @@ -0,0 +1,46 @@ +# Encoding: utf-8 +require 'rspec/expectations' +require 'chefspec' +require 'chefspec/berkshelf' +require 'chef/application' +require 'json' + +Dir['./test/unit/spec/support/**/*.rb'].sort.each { |f| require f } + +::LOG_LEVEL = :fatal +::CHEFSPEC_OPTS = { + log_level: ::LOG_LEVEL +} + +# use node.default or node.set to put stub data for every node in every test +# could also use this method to stub other node-related things like environment +def node_resources(node) + # Stub the node and any calls to Environment.Load to return this environment + env = Chef::Environment.new + env.name 'chefspec' # matches ./test/integration/ + allow(node).to receive(:chef_environment).and_return(env.name) + allow(Chef::Environment).to receive(:load).and_return(env) +end + +# use to stub commands or files or other ruby calls +# e.g. stub_command('/usr/sbin/httpd -t').and_return(0) +def stub_resources +end + +def stub_chef_zero(platform, version, server) + Dir['./test/fixtures/nodes/*.json'].sort.each do |f| + node_data = JSON.parse(IO.read(f), symbolize_names: false) + node_name = node_data['name'] + server.create_node(node_name, node_data) + platform.to_s # pacify rubocop + version.to_s # pacify rubocop + end + + Dir['./test/fixtures/environments/*.json'].sort.each do |f| + env_data = JSON.parse(IO.read(f), symbolize_names: false) + env_name = env_data['name'] + server.create_environment(env_name, env_data) + end +end + +at_exit { ChefSpec::Coverage.report! } diff --git a/test/unit/spec/support/platform_properties.rb b/test/unit/spec/support/platform_properties.rb new file mode 100644 index 000000000..29cf7b48d --- /dev/null +++ b/test/unit/spec/support/platform_properties.rb @@ -0,0 +1,9 @@ +require 'json' + +def load_platform_properties(args) + platform_file_str = "../../../fixtures/platform/#{args[:platform]}/#{args[:platform_version]}.json" + platform_file_name = File.join(File.dirname(__FILE__), platform_file_str) + platform_str = '{}' + platform_str = IO.read(platform_file_name) if File.exist?(platform_file_name) + JSON.parse(platform_str, symbolize_names: false) +end diff --git a/test/unit/spec/support/supported_platforms.rb b/test/unit/spec/support/supported_platforms.rb new file mode 100644 index 000000000..753403fe5 --- /dev/null +++ b/test/unit/spec/support/supported_platforms.rb @@ -0,0 +1,6 @@ +def supported_platforms + { + 'ubuntu' => ['12.04', '14.04'], + 'centos' => ['6.5'] + } +end