chefspec 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/chefspec.rb +9 -0
- data/lib/chefspec/chef_runner.rb +95 -0
- data/lib/chefspec/matchers/execute.rb +13 -0
- data/lib/chefspec/matchers/file.rb +16 -0
- data/lib/chefspec/matchers/log.rb +13 -0
- data/lib/chefspec/matchers/package.rb +16 -0
- data/lib/chefspec/matchers/service.rb +16 -0
- data/lib/chefspec/matchers/shared.rb +33 -0
- data/lib/chefspec/monkey_patches/hash.rb +16 -0
- metadata +73 -0
data/lib/chefspec.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'chef'
|
2
|
+
require 'chefspec/chef_runner'
|
3
|
+
require 'chefspec/matchers/execute'
|
4
|
+
require 'chefspec/matchers/file'
|
5
|
+
require 'chefspec/matchers/log'
|
6
|
+
require 'chefspec/matchers/package'
|
7
|
+
require 'chefspec/matchers/service'
|
8
|
+
require 'chefspec/matchers/shared'
|
9
|
+
require 'chefspec/monkey_patches/hash'
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'chef'
|
2
|
+
require 'chef/client'
|
3
|
+
require 'chef/cookbook_loader'
|
4
|
+
require 'chefspec/matchers/shared'
|
5
|
+
|
6
|
+
# ChefSpec allows you to write rspec examples for Chef recipes to gain faster feedback without the need to converge a
|
7
|
+
# node.
|
8
|
+
module ChefSpec
|
9
|
+
|
10
|
+
# The main entry point for running recipes within RSpec.
|
11
|
+
class ChefRunner
|
12
|
+
|
13
|
+
@step_into = []
|
14
|
+
@resources = []
|
15
|
+
|
16
|
+
attr_reader :resources
|
17
|
+
attr_reader :node
|
18
|
+
|
19
|
+
# Instantiate a new runner to run examples with.
|
20
|
+
#
|
21
|
+
# @param [string] cookbook_path The path to the chef cookbook(s) to be tested
|
22
|
+
def initialize(cookbook_path=default_cookbook_path)
|
23
|
+
the_runner = self
|
24
|
+
|
25
|
+
Chef::Resource.class_eval do
|
26
|
+
alias :old_run_action :run_action
|
27
|
+
|
28
|
+
@@runner = the_runner
|
29
|
+
|
30
|
+
def run_action(action)
|
31
|
+
@@runner.resources << self
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
Chef::Config[:solo] = true
|
36
|
+
Chef::Config[:cookbook_path] = cookbook_path
|
37
|
+
Chef::Log.verbose = true
|
38
|
+
Chef::Log.level(:debug)
|
39
|
+
@client = Chef::Client.new
|
40
|
+
@client.run_ohai
|
41
|
+
@node = @client.build_node
|
42
|
+
end
|
43
|
+
|
44
|
+
# Infer the default cookbook path from the location of the calling spec.
|
45
|
+
#
|
46
|
+
# @return [String] The path to the cookbooks directory
|
47
|
+
def default_cookbook_path
|
48
|
+
File.join(caller(2).first.split(':').slice(0..-3).to_s, "..", "..", "..")
|
49
|
+
end
|
50
|
+
|
51
|
+
# Run the specified recipes, but without actually converging the node.
|
52
|
+
#
|
53
|
+
# @param [array] recipe_names The names of the recipes to execute
|
54
|
+
def converge(*recipe_names)
|
55
|
+
recipe_names.each do |recipe_name|
|
56
|
+
@client.node.run_list << recipe_name
|
57
|
+
end
|
58
|
+
|
59
|
+
@resources = []
|
60
|
+
run_context = Chef::RunContext.new(@client.node, Chef::CookbookCollection.new(Chef::CookbookLoader.new))
|
61
|
+
|
62
|
+
runner = Chef::Runner.new(run_context)
|
63
|
+
runner.converge
|
64
|
+
end
|
65
|
+
|
66
|
+
# Find any directory declared with the given path
|
67
|
+
#
|
68
|
+
# @param [String] path The directory path
|
69
|
+
# @return [Chef::Resource::Directory] The matching directory, or Nil
|
70
|
+
def directory(path)
|
71
|
+
find_resource('directory', path)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Find any file declared with the given path
|
75
|
+
#
|
76
|
+
# @param [String] path The file path
|
77
|
+
# @return [Chef::Resource::Directory] The matching file, or Nil
|
78
|
+
def file(path)
|
79
|
+
find_resource('file', path)
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
# Find the resource with the declared type and name
|
85
|
+
#
|
86
|
+
# @param [String] type The type of resource - e.g. 'file' or 'directory'
|
87
|
+
# @param [String] name The resource name
|
88
|
+
# @return [Chef::Resource] The matching resource, or Nil
|
89
|
+
def find_resource(type, name)
|
90
|
+
resources.find{|resource| resource_type(resource) == type and resource.name == name}
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'chefspec/matchers/shared'
|
2
|
+
|
3
|
+
module ChefSpec
|
4
|
+
module Matchers
|
5
|
+
RSpec::Matchers.define :execute_command do |command|
|
6
|
+
match do |chef_run|
|
7
|
+
chef_run.resources.any? do |resource|
|
8
|
+
resource_type(resource) == 'execute' and resource.command == command
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'chefspec/matchers/shared'
|
2
|
+
|
3
|
+
module ChefSpec
|
4
|
+
module Matchers
|
5
|
+
|
6
|
+
define_resource_matchers([:create, :delete], [:file, :directory], :path)
|
7
|
+
|
8
|
+
RSpec::Matchers.define :be_owned_by do |user, group|
|
9
|
+
match do |file|
|
10
|
+
file.owner == user and file.group == group
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'chefspec/matchers/shared'
|
2
|
+
|
3
|
+
module ChefSpec
|
4
|
+
module Matchers
|
5
|
+
RSpec::Matchers.define :log do |message|
|
6
|
+
match do |chef_run|
|
7
|
+
chef_run.resources.any? do |resource|
|
8
|
+
resource_type(resource) == 'log' and resource.name == message
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'chefspec/matchers/shared'
|
2
|
+
|
3
|
+
module ChefSpec
|
4
|
+
module Matchers
|
5
|
+
|
6
|
+
define_resource_matchers([:install, :remove, :upgrade, :purge], [:package], :package_name)
|
7
|
+
|
8
|
+
RSpec::Matchers.define :install_package_at_version do |package_name, version|
|
9
|
+
match do |chef_run|
|
10
|
+
chef_run.resources.any? do |resource|
|
11
|
+
resource_type(resource) == 'package' and resource.package_name == package_name and resource.action.to_s == 'install' and resource.version == version
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'chefspec/matchers/shared'
|
2
|
+
|
3
|
+
module ChefSpec
|
4
|
+
module Matchers
|
5
|
+
|
6
|
+
define_resource_matchers([:start, :stop, :restart, :reload], [:service], :service_name)
|
7
|
+
|
8
|
+
RSpec::Matchers.define :set_service_to_start_on_boot do |service|
|
9
|
+
match do |chef_run|
|
10
|
+
chef_run.resources.any? do |resource|
|
11
|
+
resource_type(resource) == 'service' and resource.service_name == service and resource.action.include? :enable
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# Given a resource return the unqualified type it is
|
2
|
+
#
|
3
|
+
# @param [String] resource A Chef Resource
|
4
|
+
# @return [String] The resource type
|
5
|
+
def resource_type(resource)
|
6
|
+
resource.class.name.split("::").last.downcase
|
7
|
+
end
|
8
|
+
|
9
|
+
# Define simple RSpec matchers for the product of resource types and actions
|
10
|
+
#
|
11
|
+
# @param [Array] actions The valid actions - for example [:create, :delete]
|
12
|
+
# @param [Array] resource_types The resource types
|
13
|
+
# @param [Symbol] name_attribute The name attribute of the resource types
|
14
|
+
def define_resource_matchers(actions, resource_types, name_attribute)
|
15
|
+
actions.product(resource_types).flatten.each_slice(2) do |action, resource_type|
|
16
|
+
RSpec::Matchers.define "#{action}_#{resource_type}".to_sym do |name|
|
17
|
+
match do |chef_run|
|
18
|
+
accepted_types = [resource_type.to_s]
|
19
|
+
accepted_types << 'template' if action.to_s == 'create' and resource_type.to_s == 'file'
|
20
|
+
chef_run.resources.any? do |resource|
|
21
|
+
accepted_types.include? resource_type(resource) and resource.send(name_attribute) == name and
|
22
|
+
resource.action.to_s.include? action.to_s
|
23
|
+
end
|
24
|
+
end
|
25
|
+
failure_message_for_should do |actual|
|
26
|
+
"No #{resource_type} resource named '#{name}' with action :#{action} found."
|
27
|
+
end
|
28
|
+
failure_message_for_should_not do |actual|
|
29
|
+
"Found #{resource_type} resource named '#{name}' with action :#{action} that should not exist."
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# Ruby stdlib Hash class
|
2
|
+
class Hash
|
3
|
+
|
4
|
+
# Monkey-patch to stdlib Hash to give us auto-vivifying hashes like Chef.
|
5
|
+
# @param [Symbol] method_id The method name
|
6
|
+
def method_missing(method_id)
|
7
|
+
key = method_id.id2name
|
8
|
+
if has_key?(key)
|
9
|
+
self[key]
|
10
|
+
elsif has_key?(key.to_sym)
|
11
|
+
self[key.to_sym]
|
12
|
+
else
|
13
|
+
super.method_missing(method_id)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
metadata
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: chefspec
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Andrew Crump
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-07-31 00:00:00 Z
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: Write RSpec examples for Opscode Chef recipes
|
22
|
+
email:
|
23
|
+
executables: []
|
24
|
+
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files: []
|
28
|
+
|
29
|
+
files:
|
30
|
+
- lib/chefspec/chef_runner.rb
|
31
|
+
- lib/chefspec/matchers/execute.rb
|
32
|
+
- lib/chefspec/matchers/file.rb
|
33
|
+
- lib/chefspec/matchers/log.rb
|
34
|
+
- lib/chefspec/matchers/package.rb
|
35
|
+
- lib/chefspec/matchers/service.rb
|
36
|
+
- lib/chefspec/matchers/shared.rb
|
37
|
+
- lib/chefspec/monkey_patches/hash.rb
|
38
|
+
- lib/chefspec.rb
|
39
|
+
homepage: http://acrmp.github.com/chefspec
|
40
|
+
licenses:
|
41
|
+
- MIT
|
42
|
+
post_install_message:
|
43
|
+
rdoc_options: []
|
44
|
+
|
45
|
+
require_paths:
|
46
|
+
- lib
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
48
|
+
none: false
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
hash: 3
|
53
|
+
segments:
|
54
|
+
- 0
|
55
|
+
version: "0"
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
hash: 3
|
62
|
+
segments:
|
63
|
+
- 0
|
64
|
+
version: "0"
|
65
|
+
requirements: []
|
66
|
+
|
67
|
+
rubyforge_project:
|
68
|
+
rubygems_version: 1.8.6
|
69
|
+
signing_key:
|
70
|
+
specification_version: 3
|
71
|
+
summary: chefspec-0.0.1
|
72
|
+
test_files: []
|
73
|
+
|