fizzgig 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ Gemfile.lock
2
+
3
+ .ruby-version
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
data/TODO.org ADDED
@@ -0,0 +1,72 @@
1
+
2
+ * Config options used by rspec-puppet:
3
+ Puppet[:certname]
4
+ Puppet[:code]
5
+ Puppet[:config]
6
+ Puppet[:libdir]
7
+ Puppet[:manifest]
8
+ Puppet[:manifestdir]
9
+ Puppet[:modulepath]
10
+ Puppet[:templatedir]
11
+ ** rspec config added by rspec-puppet
12
+ RSpec.configure do |c|
13
+ c.add_setting :module_path, :default => '/etc/puppet/modules'
14
+ c.add_setting :manifest_dir, :default => nil
15
+ c.add_setting :manifest, :default => nil
16
+ c.add_setting :template_dir, :default => nil
17
+ c.add_setting :config, :default => nil
18
+ end
19
+ * Feature ideas
20
+ ** DONE Test defined types
21
+ ** DONE Test templates
22
+ ** DONE test for creation of namespaced types
23
+ ** DONE test that created types actually exist
24
+ ** DONE play nicely with rspec-puppet
25
+ ** DONE Stub puppet functions (inc extlookup and hiera)
26
+ ** DONE Test classes
27
+ ** DONE Stub facts
28
+
29
+ from rspec-puppet?
30
+
31
+ node_obj = Puppet::Node.new(nodename) # default to Puppet[:certname]?
32
+ node_obj.merge(facts_val) # but what is this?
33
+
34
+ ** TODO Document it!
35
+ ** TODO Ensure that modulepath can take multiple directories
36
+ - in govuk/puppet, we had trouble having multiple directories on
37
+ the module path, so we ended up doing this:
38
+
39
+ #+BEGIN_SRC ruby
40
+ RSpec.configure do |c|
41
+ c.module_path = File.join(HERE, 'modules')
42
+ # ...
43
+ end
44
+
45
+ # note monkey-patch here because the modulepath isn't working
46
+ module RSpec::Puppet
47
+ module Support
48
+ alias_method :real_build_catalog, :build_catalog
49
+ def build_catalog (nodename, fact_val, code)
50
+ Puppet[:modulepath] = File.join(HERE, 'modules') + ':' + File.join(HERE, 'vendor', 'modules')
51
+ real_build_catalog(nodename,fact_val,code)
52
+ end
53
+ end
54
+ end
55
+ #+END_SRC
56
+
57
+ That's not so cool :(
58
+
59
+ ** TODO Rename
60
+ ** TODO Release it!
61
+ ** TODO implementation-independent dependency assertions
62
+ ** TODO Test standalone puppet modules
63
+ ** TODO better test output for files with large content
64
+ ** TODO Test puppet types (ie from lib/puppet/parser/types)
65
+ ** TODO Test templates in isolation
66
+ ** TODO Test custom facts
67
+ ** TODO Virtual/exported resources
68
+ ** TODO Parameterized classes
69
+ ** TODO Preconditions (do I actually want this?)
70
+ ** TODO nodes
71
+ See govuk_nodes_spec_optional for examples of this.
72
+ the rspec-puppet equivalent is :type => :host
data/fizzgig.gemspec ADDED
@@ -0,0 +1,17 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'fizzgig'
3
+ s.version = '0.1.0'
4
+ s.homepage = 'https://github.com/philandstuff/fizzgig'
5
+ s.summary = 'Tools for writing fast unit tests for Puppet'
6
+ s.description = 'Tools for writing fast unit tests for Puppet'
7
+
8
+ s.files = `git ls-files`.split("\n")
9
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
10
+
11
+ s.add_dependency 'puppet'
12
+ s.add_dependency 'lspace'
13
+ s.add_development_dependency 'rspec'
14
+
15
+ s.authors = ['Philip Potter']
16
+ s.email = 'philip.g.potter@gmail.com'
17
+ end
data/lib/fizzgig.rb ADDED
@@ -0,0 +1,66 @@
1
+ require 'puppet'
2
+ require 'fizzgig/matchers'
3
+ require 'fizzgig/function_stubs'
4
+ require 'lspace'
5
+
6
+ module Fizzgig
7
+ def self.instantiate(code,options = {})
8
+ LSpace.with(:function_stubs => options[:stubs]) do
9
+ setup_puppet
10
+ compiler = make_compiler(options[:facts])
11
+ resources = compile(code,compiler)
12
+ resources[0].evaluate
13
+ compiler.catalog
14
+ end
15
+ end
16
+
17
+ def self.include(klass,options = {})
18
+ LSpace.with(:function_stubs => options[:stubs]) do
19
+ setup_puppet
20
+ compiler = make_compiler(options[:facts])
21
+ compile("include #{klass}",compiler)
22
+ compiler.catalog
23
+ end
24
+ end
25
+
26
+ def self.make_compiler(facts)
27
+ node = Puppet::Node.new('localhost')
28
+ node.merge(facts) if facts
29
+ compiler = Puppet::Parser::Compiler.new(node)
30
+ compiler.send :set_node_parameters
31
+ compiler.send :evaluate_main
32
+ compiler
33
+ end
34
+
35
+ def self.compile(code,compiler)
36
+ resources_for(ast_for(code,compiler),compiler)
37
+ end
38
+
39
+ def self.resources_for(ast,compiler)
40
+ scope = compiler.newscope(nil)
41
+ scope.source = Puppet::Resource::Type.new(:node,'localhost')
42
+ ast.code[0].evaluate(scope)
43
+ end
44
+
45
+ def self.ast_for(code,compiler)
46
+ parser = Puppet::Parser::Parser.new compiler.environment.name
47
+ parser.parse(code)
48
+ end
49
+
50
+ def self.setup_puppet
51
+ Puppet[:manifestdir] = ''
52
+ Puppet[:modulepath] = RSpec.configuration.modulepath
53
+ # stop template() fn from complaining about missing vardir config
54
+ Puppet[:templatedir] = ""
55
+ end
56
+ end
57
+
58
+ RSpec.configure do |c|
59
+ c.add_setting :modulepath, :default => '/etc/puppet/modules'
60
+
61
+ # FIXME: decide if these are needed
62
+ #c.add_setting :manifestdir, :default => nil
63
+ #c.add_setting :manifest, :default => nil
64
+ #c.add_setting :template_dir, :default => nil
65
+ #c.add_setting :config, :default => nil
66
+ end
@@ -0,0 +1,29 @@
1
+
2
+ module Fizzgig::FunctionStubs
3
+ def self.has_stub?(fname,args)
4
+ stubs = LSpace[:function_stubs] || {}
5
+ stubs.has_key?(fname.to_sym) &&
6
+ stubs[fname.to_sym].has_key?(args[0])
7
+ end
8
+
9
+ def self.get_stub(fname,args)
10
+ LSpace[:function_stubs][fname.to_sym][args[0]]
11
+ end
12
+ end
13
+
14
+ class Puppet::Parser::AST
15
+ class Function < AST::Branch
16
+ alias_method :orig_evaluate, :evaluate
17
+
18
+ def evaluate(scope)
19
+ #FIXME: are there implications around potential
20
+ #double-evaluation here?
21
+ args = @arguments.safeevaluate(scope).map { |x| x == :undef ? '' : x }
22
+ if Fizzgig::FunctionStubs.has_stub?(@name,args)
23
+ Fizzgig::FunctionStubs.get_stub(@name,args)
24
+ else
25
+ orig_evaluate(scope)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,67 @@
1
+ require 'rspec'
2
+ module Fizzgig
3
+ module CatalogMatchers
4
+ extend RSpec::Matchers::DSL
5
+
6
+ class CatalogMatcher
7
+ # matcher :contain_file do |expected_title|
8
+ def initialize(expected_type,expected_title)
9
+ @expected_type = expected_type
10
+ @expected_title = expected_title
11
+ @referenced_type = referenced_type(expected_type)
12
+ end
13
+
14
+ def matches?(catalog)
15
+ @catalog = catalog
16
+ resource = catalog.resource(@referenced_type,@expected_title)
17
+ if resource then
18
+ (@expected_params || {}).all? do |name,expected_val|
19
+ if expected_val.kind_of?(Regexp)
20
+ resource[name] =~ expected_val
21
+ else
22
+ resource[name] == expected_val
23
+ end
24
+ end
25
+ else
26
+ false
27
+ end
28
+ end
29
+
30
+ def failure_message_for_should
31
+ param_string = ""
32
+ if @expected_params
33
+ param_string = " with parameters #{@expected_params.inspect}"
34
+ end
35
+ possible_resource = @catalog.resource(@referenced_type,@expected_title)
36
+ actual = possible_resource ? possible_resource.inspect : "the catalog"
37
+ "expected #{actual} to contain #{@referenced_type}[#{@expected_title}]#{param_string}"
38
+ end
39
+
40
+ def method_missing(method, *args, &block)
41
+ if method.to_s =~ /^with_/
42
+ param = method.to_s.gsub(/^with_/,'')
43
+ @expected_params ||= {}
44
+ @expected_params[param] = args[0]
45
+ self
46
+ else
47
+ super
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def referenced_type(type)
54
+ type.split('__').map { |r| r.capitalize }.join('::')
55
+ end
56
+ end
57
+
58
+ def method_missing(method, *args, &block)
59
+ if method.to_s =~ /^contain_/
60
+ resource_type = method.to_s.gsub(/^contain_/,'')
61
+ CatalogMatcher.new(resource_type,args[0])
62
+ else
63
+ super
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,111 @@
1
+ require 'spec_helper'
2
+ require 'fizzgig'
3
+
4
+ describe Fizzgig do
5
+ context 'when instantiating defined types' do
6
+ it 'should test existence of file with parameters' do
7
+ instance_code = %q[nginx::site {'foo': content => 'dontcare'}]
8
+ instance = Fizzgig.instantiate(instance_code)
9
+ instance.should contain_file('/etc/nginx/sites-enabled/foo').
10
+ with_ensure('present').
11
+ with_mode('0440')
12
+ end
13
+
14
+ it 'should test resources other than files' do
15
+ instance_code = %q[nginx::site {'foo': content => 'dontcare'}]
16
+ instance = Fizzgig.instantiate(instance_code)
17
+ instance.should contain_user('www-data')
18
+ end
19
+
20
+ it 'should test presence of namespaced type' do
21
+ instance_code = %q[nginx::simple_server {'foo':}]
22
+ instance = Fizzgig.instantiate(instance_code)
23
+ instance.should contain_nginx__site('foo')
24
+ end
25
+
26
+ it 'should test content from a template' do
27
+ instance_code = %q[nginx::simple_server {'foo':}]
28
+ instance = Fizzgig.instantiate(instance_code)
29
+ instance.should contain_nginx__site('foo').
30
+ with_content(/server_name foo;/)
31
+ end
32
+ end
33
+
34
+ it 'should test classes' do
35
+ catalog = Fizzgig.include 'webapp'
36
+ catalog.should contain_nginx__site('webapp')
37
+ end
38
+
39
+ context 'when stubbing function calls' do
40
+ it 'should return the value given' do
41
+ stubs = {:extlookup => {'ssh-key-barry' => 'the key of S'}}
42
+ catalog = Fizzgig.include('functions::class_test', :stubs => stubs)
43
+ catalog.should contain_ssh_authorized_key('barry').with_key('the key of S')
44
+ end
45
+
46
+ it 'should return the value given when instantiating a defined type' do
47
+ stubs = {:extlookup => {'ssh-key-barry' => 'the key of S'}}
48
+ catalog = Fizzgig.instantiate(%[functions::define_test{'foo': }], :stubs => stubs)
49
+ catalog.should contain_ssh_authorized_key('barry').with_key('the key of S')
50
+ end
51
+
52
+ context 'when stubbing data different to that provided' do
53
+ it 'should throw an exception' do
54
+ stubs = {:extlookup => {'bananas' => 'potassium'}}
55
+ expect { catalog = Fizzgig.include('functions::class_test',:stubs=>stubs) }.
56
+ to raise_error Puppet::Error
57
+ end
58
+ end
59
+ end
60
+
61
+ context 'when providing recursive stubs' do
62
+ it 'should return the value given' do
63
+ stubs = {:extlookup =>
64
+ { 'ssh-key-barry' => 'rsa-key-barry',
65
+ 'rsa-key-barry' => 'the key of S'}}
66
+ Fizzgig.include('functions::recursive_extlookup_test', :stubs => stubs).
67
+ should contain_ssh_authorized_key('barry').with_key('the key of S')
68
+ end
69
+ end
70
+
71
+ context 'when stubbing facts' do
72
+ context 'while instantiating defined types' do
73
+ it 'should lookup unqualified fact from stub' do
74
+ catalog = Fizzgig.instantiate(%q[facts::define_test{'test':}], :facts => {'unqualified_fact' => 'hello world'})
75
+ catalog.should contain_notify('unqualified-fact-test').with_message('hello world')
76
+ end
77
+
78
+ it 'should lookup qualified fact from stub' do
79
+ catalog = Fizzgig.instantiate(%q[facts::define_test{'test':}], :facts => {'qualified_fact' => 'hello world'})
80
+ catalog.should contain_notify('qualified-fact-test').with_message('hello world')
81
+ end
82
+ end
83
+
84
+ context 'while including classes' do
85
+ it 'should lookup unqualified fact from stub' do
86
+ catalog = Fizzgig.include('facts::class_test', :facts => {'unqualified_fact' => 'hello world'})
87
+ catalog.should contain_notify('unqualified-fact-test').with_message('hello world')
88
+ end
89
+
90
+ it 'should lookup qualified fact from stub' do
91
+ catalog = Fizzgig.include('facts::class_test', :facts => {'qualified_fact' => 'hello world'})
92
+ catalog.should contain_notify('qualified-fact-test').with_message('hello world')
93
+ end
94
+
95
+ it 'should lookup fact by instance variable from within template' do
96
+ catalog = Fizzgig.include('facts::template_test', :facts => {'template_visible_fact' => 'hello world'})
97
+ catalog.should contain_file('template-test').with_content(/instance_fact:hello world/)
98
+ end
99
+
100
+ it 'should lookup fact by accessor method from within template' do
101
+ catalog = Fizzgig.include('facts::template_test', :facts => {'template_visible_fact' => 'hello world'})
102
+ catalog.should contain_file('template-test').with_content(/accessor_fact:hello world/)
103
+ end
104
+
105
+ it 'should lookup fact by scope.lookupvar from within template' do
106
+ catalog = Fizzgig.include('facts::template_test', :facts => {'template_visible_fact' => 'hello world'})
107
+ catalog.should contain_file('template-test').with_content(/scope_lookup_fact:hello world/)
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,8 @@
1
+ class facts::class_test {
2
+ notify {
3
+ 'unqualified-fact-test':
4
+ message => $unqualified_fact;
5
+ 'qualified-fact-test':
6
+ message => $::qualified_fact;
7
+ }
8
+ }
@@ -0,0 +1,8 @@
1
+ define facts::define_test() {
2
+ notify {
3
+ 'unqualified-fact-test':
4
+ message => $unqualified_fact;
5
+ 'qualified-fact-test':
6
+ message => $::qualified_fact;
7
+ }
8
+ }
@@ -0,0 +1,5 @@
1
+ class facts::template_test {
2
+ file {'template-test':
3
+ content => template('facts/template-test.erb'),
4
+ }
5
+ }
@@ -0,0 +1,3 @@
1
+ instance_fact:<%= @template_visible_fact %>
2
+ accessor_fact:<%= template_visible_fact %>
3
+ scope_lookup_fact:<%= scope.lookupvar('template_visible_fact') %>
@@ -0,0 +1,5 @@
1
+ class functions::class_test {
2
+ ssh_authorized_key{'barry':
3
+ key => extlookup('ssh-key-barry'),
4
+ }
5
+ }
@@ -0,0 +1,5 @@
1
+ define functions::define_test () {
2
+ ssh_authorized_key{'barry':
3
+ key => extlookup('ssh-key-barry'),
4
+ }
5
+ }
@@ -0,0 +1,5 @@
1
+ class functions::recursive_extlookup_test {
2
+ ssh_authorized_key{'barry':
3
+ key => extlookup(extlookup('ssh-key-barry')),
4
+ }
5
+ }
@@ -0,0 +1,5 @@
1
+ define nginx::simple_server () {
2
+ nginx::site{$title:
3
+ content => template('nginx/vhost.erb'),
4
+ }
5
+ }
@@ -0,0 +1,9 @@
1
+ define nginx::site ($content) {
2
+ file {"/etc/nginx/sites-enabled/$title":
3
+ ensure => present,
4
+ mode => 0440,
5
+ content => template('nginx/vhost.erb'),
6
+ }
7
+ user {'www-data':
8
+ }
9
+ }
@@ -0,0 +1,5 @@
1
+ server {
2
+ listen 80;
3
+ server_name <%= @title %>;
4
+ root /var/cache/www;
5
+ }
@@ -0,0 +1,4 @@
1
+ class webapp {
2
+ nginx::site {'webapp':
3
+ }
4
+ }
@@ -0,0 +1,10 @@
1
+ require 'rspec/autorun'
2
+ require 'fizzgig'
3
+
4
+ HERE = File.expand_path(File.dirname(__FILE__))
5
+
6
+ RSpec.configure do |c|
7
+ c.modulepath = File.join(HERE, 'modules')
8
+
9
+ c.include Fizzgig::CatalogMatchers
10
+ end
metadata ADDED
@@ -0,0 +1,125 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fizzgig
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Philip Potter
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-03-16 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: puppet
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: lspace
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: Tools for writing fast unit tests for Puppet
63
+ email: philip.g.potter@gmail.com
64
+ executables: []
65
+ extensions: []
66
+ extra_rdoc_files: []
67
+ files:
68
+ - .gitignore
69
+ - Gemfile
70
+ - TODO.org
71
+ - fizzgig.gemspec
72
+ - lib/fizzgig.rb
73
+ - lib/fizzgig/function_stubs.rb
74
+ - lib/fizzgig/matchers.rb
75
+ - spec/fizzgig_spec.rb
76
+ - spec/modules/facts/manifests/class_test.pp
77
+ - spec/modules/facts/manifests/define_test.pp
78
+ - spec/modules/facts/manifests/template_test.pp
79
+ - spec/modules/facts/templates/template-test.erb
80
+ - spec/modules/functions/manifests/class_test.pp
81
+ - spec/modules/functions/manifests/define_test.pp
82
+ - spec/modules/functions/manifests/recursive_extlookup_test.pp
83
+ - spec/modules/nginx/manifests/simple_server.pp
84
+ - spec/modules/nginx/manifests/site.pp
85
+ - spec/modules/nginx/templates/vhost.erb
86
+ - spec/modules/webapp/manifests/init.pp
87
+ - spec/spec_helper.rb
88
+ homepage: https://github.com/philandstuff/fizzgig
89
+ licenses: []
90
+ post_install_message:
91
+ rdoc_options: []
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ! '>='
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ none: false
102
+ requirements:
103
+ - - ! '>='
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ requirements: []
107
+ rubyforge_project:
108
+ rubygems_version: 1.8.23
109
+ signing_key:
110
+ specification_version: 3
111
+ summary: Tools for writing fast unit tests for Puppet
112
+ test_files:
113
+ - spec/fizzgig_spec.rb
114
+ - spec/modules/facts/manifests/class_test.pp
115
+ - spec/modules/facts/manifests/define_test.pp
116
+ - spec/modules/facts/manifests/template_test.pp
117
+ - spec/modules/facts/templates/template-test.erb
118
+ - spec/modules/functions/manifests/class_test.pp
119
+ - spec/modules/functions/manifests/define_test.pp
120
+ - spec/modules/functions/manifests/recursive_extlookup_test.pp
121
+ - spec/modules/nginx/manifests/simple_server.pp
122
+ - spec/modules/nginx/manifests/site.pp
123
+ - spec/modules/nginx/templates/vhost.erb
124
+ - spec/modules/webapp/manifests/init.pp
125
+ - spec/spec_helper.rb