fizzgig 0.1.0

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/.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