chef-umami 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +22 -2
- data/bin/umami +3 -3
- data/lib/chef-umami/client.rb +0 -3
- data/lib/chef-umami/exceptions.rb +0 -2
- data/lib/chef-umami/helpers/filetools.rb +22 -24
- data/lib/chef-umami/helpers/inspec.rb +23 -24
- data/lib/chef-umami/helpers/os.rb +9 -10
- data/lib/chef-umami/logger.rb +0 -1
- data/lib/chef-umami/options.rb +72 -0
- data/lib/chef-umami/policyfile/exporter.rb +2 -8
- data/lib/chef-umami/policyfile/uploader.rb +2 -9
- data/lib/chef-umami/runner.rb +37 -17
- data/lib/chef-umami/server.rb +0 -1
- data/lib/chef-umami/test.rb +2 -4
- data/lib/chef-umami/test/integration.rb +7 -10
- data/lib/chef-umami/test/unit.rb +5 -8
- data/lib/chef-umami/version.rb +1 -1
- data/spec/runner_spec.rb +2 -2
- data/spec/spec_helper.rb +0 -103
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 782fcbb03ba84bce326bfc8d8c3e51b4a36f4c1e
|
4
|
+
data.tar.gz: c7fa44a081c7462509665637bcf1414249f35975
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f6f76421bb97b53f93b3fedac539aa4d805d556cde66a36d78fb0acc3e55299df479d7285e696cdeca4597a0aabd7e95eac098d0c41539820cb78478a93d17ff
|
7
|
+
data.tar.gz: f17e6d3eaf84166fbb2a96578ec5274b97bfeedc815b2db512a6bce12b3e7edb17a9852319be574325d51c009eb53980f53f92dc64ce672cf372dd37567754d9
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -7,6 +7,26 @@ Let's see it in action!
|
|
7
7
|
|
8
8
|
[![asciicast](https://asciinema.org/a/138816.png)](https://asciinema.org/a/138816)
|
9
9
|
|
10
|
+
## Running `umami`
|
11
|
+
|
12
|
+
A number of options maybe specified on the command line:
|
13
|
+
|
14
|
+
```bash
|
15
|
+
Usage: umami [options]
|
16
|
+
|
17
|
+
A taste you won't forget!
|
18
|
+
|
19
|
+
-h, --help Prints this help message
|
20
|
+
-i, --[no-]integration-tests Write integration tests (DEFAULT: true)
|
21
|
+
-p, --policyfile POLICYFILE_PATH Specify the path to a policy (DEFAULT: Policyfile.rb)
|
22
|
+
-r, --recipes RECIPE1,RECIPE2 Specify one or more recipes for which we'll write tests (DEFAULT: All recipes)
|
23
|
+
-t, --test-root TEST_ROOT_PATH Specify the path into which we'll write tests (DEFAULT: spec)
|
24
|
+
-u, --[no-]unit-tests Write unit tests (DEFAULT: true)
|
25
|
+
-v, --version Show version and exit
|
26
|
+
```
|
27
|
+
|
28
|
+
If not options are specified, a reasonable set of defaults are defined.
|
29
|
+
|
10
30
|
## How does it Work?
|
11
31
|
|
12
32
|
`umami` loads up one or more cookbooks in a `chef-zero` instance, executes the
|
@@ -160,11 +180,11 @@ can build on. **Do NOT depend solely on `umami` to provide test coverage!**
|
|
160
180
|
|
161
181
|
This project came to be largely out of fear of having to write a lot of test
|
162
182
|
code from scratch where none had previously existed. The idea of starting from
|
163
|
-
nothing seemed so daunting that it's likely no
|
183
|
+
nothing seemed so daunting that it's likely no one would ever get started. I
|
164
184
|
wanted to give Chef developers a means to expedite writing tests. After all,
|
165
185
|
it's much easier to modify code than it is to write it in the first place.
|
166
186
|
|
167
|
-
`umami` is the product of
|
187
|
+
`umami` is the product of research into various projects' code, such as
|
168
188
|
Chef, ChefDK, and Test Kitchen. I am grateful to everyone that has contributed
|
169
189
|
to those projects. `umami` borrows some patterns from those projects and, in
|
170
190
|
some cases, bits of code.
|
data/bin/umami
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
#!/opt/chefdk/embedded/bin/ruby
|
2
2
|
|
3
|
-
chef_umami_lib = File.expand_path(File.join(File.dirname(__FILE__),
|
4
|
-
|
5
|
-
require
|
3
|
+
chef_umami_lib = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
|
4
|
+
$LOAD_PATH.unshift(chef_umami_lib)
|
5
|
+
require 'chef-umami/runner'
|
6
6
|
|
7
7
|
Umami::Runner.new.run
|
data/lib/chef-umami/client.rb
CHANGED
@@ -16,8 +16,6 @@ require 'chef'
|
|
16
16
|
|
17
17
|
module Umami
|
18
18
|
class Client
|
19
|
-
|
20
|
-
attr_reader :client
|
21
19
|
def initialize
|
22
20
|
@client = client
|
23
21
|
end
|
@@ -45,6 +43,5 @@ module Umami
|
|
45
43
|
def resource_collection
|
46
44
|
client.run_status.run_context.resource_collection
|
47
45
|
end
|
48
|
-
|
49
46
|
end
|
50
47
|
end
|
@@ -15,32 +15,30 @@
|
|
15
15
|
module Umami
|
16
16
|
module Helper
|
17
17
|
module FileTools
|
18
|
+
require 'fileutils'
|
19
|
+
require 'rubocop'
|
18
20
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
f.write(content)
|
27
|
-
f.close
|
28
|
-
end
|
29
|
-
|
30
|
-
# Call Rubocop to ensure proper indentation and thus legibility.
|
31
|
-
def enforce_styling(path = 'spec/umami/')
|
32
|
-
puts "Running Rubocop over '#{path}' to enforce styling..."
|
33
|
-
r = RuboCop::CLI.new
|
34
|
-
# Don't output to STDOUT.
|
35
|
-
args = [
|
36
|
-
'--only', 'Style/IndentationWidth,Style/IndentationConsistency',
|
37
|
-
'--auto-correct',
|
38
|
-
'--out', '/dev/null',
|
39
|
-
path
|
40
|
-
]
|
41
|
-
r.run(args)
|
42
|
-
end
|
21
|
+
def write_file(path = nil, content = '')
|
22
|
+
parent_dir = File.dirname(path)
|
23
|
+
FileUtils.mkdir_p(parent_dir) unless ::File.exist?(parent_dir)
|
24
|
+
f = File.open(path, 'w') # Write with prejudice.
|
25
|
+
f.write(content)
|
26
|
+
f.close
|
27
|
+
end
|
43
28
|
|
29
|
+
# Call Rubocop to ensure proper indentation and thus legibility.
|
30
|
+
def enforce_styling(path = 'spec/umami/')
|
31
|
+
puts "Running Rubocop over '#{path}' to enforce styling..."
|
32
|
+
r = RuboCop::CLI.new
|
33
|
+
# Don't output to STDOUT.
|
34
|
+
args = [
|
35
|
+
'--only', 'Style/IndentationWidth,Style/IndentationConsistency',
|
36
|
+
'--auto-correct',
|
37
|
+
'--out', '/dev/null',
|
38
|
+
path
|
39
|
+
]
|
40
|
+
r.run(args)
|
41
|
+
end
|
44
42
|
end
|
45
43
|
end
|
46
44
|
end
|
@@ -15,14 +15,14 @@
|
|
15
15
|
module Umami
|
16
16
|
module Helper
|
17
17
|
module InSpec
|
18
|
-
|
19
18
|
# Call on a resource's #identity method to help describe the resource.
|
20
19
|
# This saves us from having to know/code the identity attribute for each
|
21
20
|
# resource (i.e. File is :path, User is :username, etc).
|
22
21
|
def desciption(resource)
|
23
|
-
identity = resource.identity
|
24
22
|
if identity.is_a? Hash # #identity could return a Hash. Take the first value.
|
25
23
|
identity = identity.values.first
|
24
|
+
else
|
25
|
+
identity = resource.identity
|
26
26
|
end
|
27
27
|
"describe #{resource.declared_type}('#{identity}') do"
|
28
28
|
end
|
@@ -34,17 +34,17 @@ module Umami
|
|
34
34
|
# (i.e. testing a directory resource requires defining a file test).
|
35
35
|
# 2. The method should should return a string joined by newlines.
|
36
36
|
#
|
37
|
-
#def test_wutang(resource)
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
41
|
-
#
|
42
|
-
#end
|
37
|
+
# def test_wutang(resource)
|
38
|
+
# test = [desciption(resource)]
|
39
|
+
# test << "it { should be_financially_sound }"
|
40
|
+
# test << "it { should be_diverisified }"
|
41
|
+
# test.join("\n")
|
42
|
+
# end
|
43
43
|
|
44
44
|
# InSpec can evaluate if a gem is installed via the system `gem` (default)
|
45
45
|
# or via some other `gem` binary, defined by either the path to the gem
|
46
46
|
# binary of a symbol representing that context.
|
47
|
-
def test_gem_package(resource, gem_binary=nil)
|
47
|
+
def test_gem_package(resource, gem_binary = nil)
|
48
48
|
package_name = resource.package_name
|
49
49
|
if gem_binary
|
50
50
|
if gem_binary.is_a? Symbol
|
@@ -76,35 +76,35 @@ module Umami
|
|
76
76
|
"#{resource.weekday} " \
|
77
77
|
"#{resource.command}"
|
78
78
|
test << "it { should have_entry('#{cron_entry}').with_user('#{resource.user}') }"
|
79
|
-
test <<
|
79
|
+
test << 'end'
|
80
80
|
test.join("\n")
|
81
81
|
end
|
82
82
|
|
83
83
|
def test_file(resource)
|
84
84
|
test = ["describe file('#{resource.path}') do"]
|
85
85
|
if resource.declared_type =~ /directory/
|
86
|
-
test <<
|
86
|
+
test << 'it { should be_directory }'
|
87
87
|
else
|
88
|
-
test <<
|
88
|
+
test << 'it { should be_file }'
|
89
89
|
end
|
90
90
|
# Sometimes we see GIDs instead of group names.
|
91
|
-
|
91
|
+
unless resource.group.nil?
|
92
92
|
unless resource.group.is_a?(String) && resource.group.empty?
|
93
93
|
test << "it { should be_grouped_into '#{resource.group}' }"
|
94
94
|
end
|
95
95
|
end
|
96
96
|
# Guard for UIDs versus usernames as well.
|
97
|
-
|
97
|
+
unless resource.owner.nil?
|
98
98
|
unless resource.owner.is_a?(String) && resource.owner.empty?
|
99
99
|
test << "it { should be_owned_by '#{resource.owner}' }"
|
100
100
|
end
|
101
101
|
end
|
102
|
-
|
102
|
+
unless resource.mode.nil?
|
103
103
|
unless resource.mode.is_a?(String) && !resource.mode.empty?
|
104
104
|
test << "it { should be_mode '#{resource.mode}' }"
|
105
105
|
end
|
106
106
|
end
|
107
|
-
test <<
|
107
|
+
test << 'end'
|
108
108
|
test.join("\n")
|
109
109
|
end
|
110
110
|
alias_method :test_cookbook_file, :test_file
|
@@ -115,8 +115,8 @@ module Umami
|
|
115
115
|
|
116
116
|
def test_group(resource)
|
117
117
|
test = [desciption(resource)]
|
118
|
-
test <<
|
119
|
-
test <<
|
118
|
+
test << 'it { should exist }'
|
119
|
+
test << 'end'
|
120
120
|
test.join("\n")
|
121
121
|
end
|
122
122
|
|
@@ -125,18 +125,18 @@ module Umami
|
|
125
125
|
if !resource.version.nil? && !resource.version.empty?
|
126
126
|
test << "it { should be_installed.with_version('#{resource.version}') }"
|
127
127
|
else
|
128
|
-
test <<
|
128
|
+
test << 'it { should be_installed }'
|
129
129
|
end
|
130
|
-
test <<
|
130
|
+
test << 'end'
|
131
131
|
test.join("\n")
|
132
132
|
end
|
133
133
|
|
134
134
|
def test_user(resource)
|
135
135
|
test = [desciption(resource)]
|
136
|
-
test <<
|
136
|
+
test << 'it { should exist }'
|
137
137
|
# Guard for GIDs rather than strings. Chef aliases the #group method
|
138
138
|
# to the #gid method.
|
139
|
-
|
139
|
+
unless resource.gid.nil?
|
140
140
|
unless resource.gid.is_a?(String) && !resource.gid.empty?
|
141
141
|
test << "it { should belong_to_primary_group '#{resource.gid}' }"
|
142
142
|
end
|
@@ -144,10 +144,9 @@ module Umami
|
|
144
144
|
if !resource.home.nil? && !resource.home.empty?
|
145
145
|
test << "it { should have_home_directory '#{resource.home}' }"
|
146
146
|
end
|
147
|
-
test <<
|
147
|
+
test << 'end'
|
148
148
|
test.join("\n")
|
149
149
|
end
|
150
|
-
|
151
150
|
end
|
152
151
|
end
|
153
152
|
end
|
@@ -27,27 +27,27 @@ module Umami
|
|
27
27
|
when /aix/
|
28
28
|
# `oslevel` => '7.1.0.0'
|
29
29
|
version = `oslevel`[0..2]
|
30
|
-
{platform: 'aix', version: version}
|
30
|
+
{ platform: 'aix', version: version }
|
31
31
|
when /darwin|mac os/
|
32
32
|
version = `sw_vers -productVersion`.chomp
|
33
|
-
{platform: 'mac_os_x', version: version}
|
33
|
+
{ platform: 'mac_os_x', version: version }
|
34
34
|
when /linux/
|
35
35
|
# Perform very basic tests to determine distribution.
|
36
36
|
if File.exist?('/etc/centos-release')
|
37
37
|
version = File.read('/etc/redhat-release').split[2]
|
38
|
-
{platform: 'centos', version: version}
|
38
|
+
{ platform: 'centos', version: version }
|
39
39
|
elsif File.exist?('/etc/redhat-release') # True for CentOS too...
|
40
40
|
version = File.read('/etc/redhat-release').split[6]
|
41
|
-
{platform: 'redhat', version: version}
|
41
|
+
{ platform: 'redhat', version: version }
|
42
42
|
else
|
43
|
-
{platform: 'centos', version: '7.3.1611'} # Default to something reasonably sane.
|
43
|
+
{ platform: 'centos', version: '7.3.1611' } # Default to something reasonably sane.
|
44
44
|
end
|
45
45
|
when /solaris/
|
46
46
|
version = `uname -r`.chomp # Release level (i.e. 5.11).
|
47
|
-
{platform: 'solaris', version: version}
|
47
|
+
{ platform: 'solaris', version: version }
|
48
48
|
when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
|
49
49
|
# Refer to https://en.wikipedia.org/wiki/Ver_(command)
|
50
|
-
win_version = `ver`.chomp.split(
|
50
|
+
win_version = `ver`.chomp.split('Version ')[1].gsub(/]/, '')
|
51
51
|
case win_version
|
52
52
|
when /^6.1.7/
|
53
53
|
version = '2008R2' # Also Win 7
|
@@ -58,14 +58,13 @@ module Umami
|
|
58
58
|
when /^(6.4|10)/
|
59
59
|
version = '10'
|
60
60
|
end
|
61
|
-
{platform: 'windows', version: version}
|
61
|
+
{ platform: 'windows', version: version }
|
62
62
|
else
|
63
63
|
# Default to something reasonably sane.
|
64
|
-
{platform: 'centos', version: '7.3.1611'}
|
64
|
+
{ platform: 'centos', version: '7.3.1611' }
|
65
65
|
end
|
66
66
|
)
|
67
67
|
end
|
68
|
-
|
69
68
|
end
|
70
69
|
end
|
71
70
|
end
|
data/lib/chef-umami/logger.rb
CHANGED
@@ -0,0 +1,72 @@
|
|
1
|
+
# Copyright 2017 Bloomberg Finance, L.P.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require 'chef-umami/version'
|
16
|
+
|
17
|
+
module Umami
|
18
|
+
module Options
|
19
|
+
# Parse command line options. Returns hash of options.
|
20
|
+
def parse_options
|
21
|
+
options = {}
|
22
|
+
# Default options
|
23
|
+
options[:integration_tests] = true
|
24
|
+
options[:policyfile] = 'Policyfile.rb'
|
25
|
+
options[:test_root] = 'spec'
|
26
|
+
options[:unit_tests] = true
|
27
|
+
|
28
|
+
parser = OptionParser.new do |opts|
|
29
|
+
opts.banner = opts.banner + "\n\nA taste you won't forget!\n\n"
|
30
|
+
opts.on('-h', '--help', 'Prints this help message') {
|
31
|
+
puts opts
|
32
|
+
exit
|
33
|
+
}
|
34
|
+
opts.on('-i', '--[no-]integration-tests', 'Write integration tests' \
|
35
|
+
" (DEFAULT: #{options[:integration_tests]})") do |integration_tests|
|
36
|
+
options[:integration_tests] = integration_tests
|
37
|
+
end
|
38
|
+
opts.on('-p', '--policyfile POLICYFILE_PATH', 'Specify the path to a policy' \
|
39
|
+
" (DEFAULT: #{options[:policyfile]})") do |policyfile|
|
40
|
+
options[:policyfile] = policyfile
|
41
|
+
end
|
42
|
+
opts.on('-r', '--recipes RECIPE1,RECIPE2', Array,
|
43
|
+
"Specify one or more recipes for which we'll write tests" \
|
44
|
+
' (DEFAULT: All recipes)') do |recipes|
|
45
|
+
options[:recipes] = recipes
|
46
|
+
end
|
47
|
+
opts.on('-t', '--test-root TEST_ROOT_PATH', "Specify the path into which we'll write tests" \
|
48
|
+
" (DEFAULT: #{options[:test_root]})") do |test_root|
|
49
|
+
options[:test_root] = test_root
|
50
|
+
end
|
51
|
+
opts.on('-u', '--[no-]unit-tests', 'Write unit tests' \
|
52
|
+
" (DEFAULT: #{options[:unit_tests]})") do |unit_tests|
|
53
|
+
options[:unit_tests] = unit_tests
|
54
|
+
end
|
55
|
+
opts.on('-v', '--version', 'Show version and exit') {
|
56
|
+
puts "chef-umami v#{Umami::VERSION}"
|
57
|
+
exit
|
58
|
+
}
|
59
|
+
end
|
60
|
+
begin
|
61
|
+
parser.parse!
|
62
|
+
rescue OptionParser::InvalidOption => e
|
63
|
+
puts "Warning: #{e.message}"
|
64
|
+
if e.message =~ /--pattern/
|
65
|
+
puts 'Ah, this is likely parsed from `rspec` options. We can safely ignore this.'
|
66
|
+
end
|
67
|
+
puts 'Ignoring the option.'
|
68
|
+
end
|
69
|
+
options
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -20,13 +20,12 @@ require 'tmpdir' # Extends Dir
|
|
20
20
|
module Umami
|
21
21
|
class Policyfile
|
22
22
|
class Exporter
|
23
|
-
|
24
23
|
attr_reader :chef_config_file
|
25
24
|
attr_reader :cookbook_dir
|
26
25
|
attr_reader :export_root
|
27
26
|
attr_reader :export_path
|
28
|
-
attr_accessor :policyfile_lock_file
|
29
27
|
attr_reader :policyfile
|
28
|
+
attr_accessor :policyfile_lock_file
|
30
29
|
|
31
30
|
def initialize(policyfile_lock_file = nil, cookbook_dir = nil, policyfile = nil)
|
32
31
|
@policyfile = policyfile
|
@@ -39,10 +38,6 @@ module Umami
|
|
39
38
|
@chef_config_file = "#{export_path}/.chef/config.rb"
|
40
39
|
end
|
41
40
|
|
42
|
-
def policyfile
|
43
|
-
@policyfile
|
44
|
-
end
|
45
|
-
|
46
41
|
def ui
|
47
42
|
@ui ||= ChefDK::UI.new
|
48
43
|
end
|
@@ -92,7 +87,7 @@ module Umami
|
|
92
87
|
rescue ChefDK::PolicyfileExportRepoError => e
|
93
88
|
puts "\nFAILED TO EXPORT POLICYFILE: #{e.message} (#{e.class})"
|
94
89
|
puts "CAUSE: #{e.cause}"
|
95
|
-
puts
|
90
|
+
puts 'BACKTRACE:'
|
96
91
|
e.backtrace.each do |line|
|
97
92
|
puts "\t#{line}"
|
98
93
|
end
|
@@ -101,7 +96,6 @@ module Umami
|
|
101
96
|
cp_fake_client_key
|
102
97
|
update_chef_config
|
103
98
|
end
|
104
|
-
|
105
99
|
end
|
106
100
|
end
|
107
101
|
end
|
@@ -21,21 +21,15 @@ require 'chef-dk/ui'
|
|
21
21
|
module Umami
|
22
22
|
class Policyfile
|
23
23
|
class Uploader
|
24
|
-
|
25
|
-
attr_reader :http_client
|
26
|
-
attr_reader :policyfile_lock
|
27
24
|
attr_reader :policyfile_lock_file
|
28
|
-
|
29
|
-
attr_reader :storage_config
|
30
|
-
attr_reader :ui
|
31
|
-
def initialize(policyfile_lock_file = nil)
|
25
|
+
def initialize(policyfile_lock_file = nil)
|
32
26
|
@http_client = http_client
|
33
27
|
@policyfile_lock_file = policyfile_lock_file
|
34
28
|
@policyfile_lock = policyfile_lock
|
35
29
|
@policyfile_uploader = policyfile_uploader
|
36
30
|
@storage_config = storage_config
|
37
31
|
@ui = ui
|
38
|
-
|
32
|
+
end
|
39
33
|
|
40
34
|
def storage_config
|
41
35
|
@storage_config ||= ChefDK::Policyfile::StorageConfig.new.use_policyfile(policyfile_lock_file)
|
@@ -86,7 +80,6 @@ module Umami
|
|
86
80
|
def upload
|
87
81
|
policyfile_uploader.upload
|
88
82
|
end
|
89
|
-
|
90
83
|
end
|
91
84
|
end
|
92
85
|
end
|
data/lib/chef-umami/runner.rb
CHANGED
@@ -16,6 +16,7 @@ require 'chef'
|
|
16
16
|
require 'chef-umami/exceptions'
|
17
17
|
require 'chef-umami/client'
|
18
18
|
require 'chef-umami/logger'
|
19
|
+
require 'chef-umami/options'
|
19
20
|
require 'chef-umami/server'
|
20
21
|
require 'chef-umami/policyfile/exporter'
|
21
22
|
require 'chef-umami/policyfile/uploader'
|
@@ -24,17 +25,13 @@ require 'chef-umami/test/integration'
|
|
24
25
|
|
25
26
|
module Umami
|
26
27
|
class Runner
|
27
|
-
|
28
28
|
include Umami::Logger
|
29
|
+
include Umami::Options
|
29
30
|
|
30
31
|
attr_reader :cookbook_dir
|
31
|
-
|
32
|
-
|
33
|
-
# TODO: Build the ability to specify a custom policy lock file name.
|
34
|
-
def initialize(policyfile_lock_file = nil, policyfile = nil)
|
32
|
+
def initialize
|
33
|
+
@config = config
|
35
34
|
@cookbook_dir = Dir.pwd
|
36
|
-
@policyfile_lock_file = 'Policyfile.lock.json'
|
37
|
-
@policyfile = policyfile || 'Policyfile.rb'
|
38
35
|
@exporter = exporter
|
39
36
|
@chef_zero_server = chef_zero_server
|
40
37
|
# If we load the uploader or client now, they won't see the updated
|
@@ -43,8 +40,23 @@ module Umami
|
|
43
40
|
@chef_client = nil
|
44
41
|
end
|
45
42
|
|
43
|
+
# A hash of values describing the config. Comprised of command line
|
44
|
+
# options. May (in the future) contain options read from a config file.
|
45
|
+
def config
|
46
|
+
@config ||= parse_options
|
47
|
+
end
|
48
|
+
|
49
|
+
def policyfile
|
50
|
+
config[:policyfile]
|
51
|
+
end
|
52
|
+
|
53
|
+
# Return the computed policyfile lock name.
|
54
|
+
def policyfile_lock_file
|
55
|
+
policyfile.gsub(/\.rb$/, '.lock.json')
|
56
|
+
end
|
57
|
+
|
46
58
|
def validate_lock_file!
|
47
|
-
unless policyfile_lock_file.end_with?(
|
59
|
+
unless policyfile_lock_file.end_with?('lock.json')
|
48
60
|
raise InvalidPolicyfileLockFilename, "Policyfile lock files must end in '.lock.json'. I received '#{policyfile_lock_file}'."
|
49
61
|
end
|
50
62
|
|
@@ -73,7 +85,7 @@ module Umami
|
|
73
85
|
validate_lock_file!
|
74
86
|
puts "\nExporting the policy, related cookbooks, and a valid client configuration..."
|
75
87
|
exporter.export
|
76
|
-
Chef::Config.from_file(
|
88
|
+
Chef::Config.from_file(exporter.chef_config_file)
|
77
89
|
chef_zero_server.start
|
78
90
|
puts "\nUploading the policy and related cookbooks..."
|
79
91
|
uploader.upload
|
@@ -86,6 +98,12 @@ module Umami
|
|
86
98
|
recipe_resources = {}
|
87
99
|
chef_client.resource_collection.each do |resource|
|
88
100
|
canonical_recipe = "#{resource.cookbook_name}::#{resource.recipe_name}"
|
101
|
+
unless config[:recipes].empty?
|
102
|
+
# The user has explicitly requested that one or more recipes have
|
103
|
+
# tests written, to the exclusion of others.
|
104
|
+
# ONLY include the recipe if it matches the list.
|
105
|
+
next unless config[:recipes].include?(canonical_recipe)
|
106
|
+
end
|
89
107
|
if recipe_resources.key?(canonical_recipe)
|
90
108
|
recipe_resources[canonical_recipe] << resource
|
91
109
|
else
|
@@ -98,15 +116,17 @@ module Umami
|
|
98
116
|
re_export_path = Regexp.new('/tmp/umami')
|
99
117
|
FileUtils.rm_rf(exporter.export_root) if exporter.export_root.match(re_export_path)
|
100
118
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
integration_tester = Umami::Test::Integration.new
|
107
|
-
integration_tester.generate(recipe_resources)
|
119
|
+
if config[:unit_tests]
|
120
|
+
puts "\nGenerating a set of unit tests..."
|
121
|
+
unit_tester = Umami::Test::Unit.new(config[:test_root])
|
122
|
+
unit_tester.generate(recipe_resources)
|
123
|
+
end
|
108
124
|
|
125
|
+
if config[:integration_tests]
|
126
|
+
puts "\nGenerating a set of integration tests..."
|
127
|
+
integration_tester = Umami::Test::Integration.new(config[:test_root])
|
128
|
+
integration_tester.generate(recipe_resources)
|
129
|
+
end
|
109
130
|
end
|
110
|
-
|
111
131
|
end
|
112
132
|
end
|
data/lib/chef-umami/server.rb
CHANGED
data/lib/chef-umami/test.rb
CHANGED
@@ -14,10 +14,9 @@
|
|
14
14
|
|
15
15
|
module Umami
|
16
16
|
class Test
|
17
|
-
|
18
17
|
attr_reader :root_dir
|
19
|
-
def initialize
|
20
|
-
@root_dir =
|
18
|
+
def initialize(root_dir)
|
19
|
+
@root_dir = root_dir
|
21
20
|
end
|
22
21
|
|
23
22
|
# All subclasses should implement the following methods.
|
@@ -58,6 +57,5 @@ module Umami
|
|
58
57
|
def generate
|
59
58
|
raise NoMethodError, "#{self.class} needs to implement the ##{__method__} method! Refer to Umami::Test."
|
60
59
|
end
|
61
|
-
|
62
60
|
end
|
63
61
|
end
|
@@ -19,12 +19,11 @@ require 'chef-umami/helpers/filetools'
|
|
19
19
|
module Umami
|
20
20
|
class Test
|
21
21
|
class Integration < Umami::Test
|
22
|
-
|
23
22
|
include Umami::Helper::InSpec
|
24
23
|
include Umami::Helper::FileTools
|
25
24
|
|
26
25
|
attr_reader :test_root
|
27
|
-
def initialize
|
26
|
+
def initialize(root_dir)
|
28
27
|
super
|
29
28
|
@test_root = File.join(self.root_dir, 'umami', 'integration')
|
30
29
|
end
|
@@ -32,7 +31,7 @@ module Umami
|
|
32
31
|
# InSpec doesn't need a require statement to use its tests.
|
33
32
|
# We define #framework here for completeness.
|
34
33
|
def framework
|
35
|
-
|
34
|
+
'inspec'
|
36
35
|
end
|
37
36
|
|
38
37
|
def test_file_path(cookbook = '', recipe = '')
|
@@ -58,10 +57,10 @@ module Umami
|
|
58
57
|
# Raise NoMethodError for any other failed calls.
|
59
58
|
def method_missing(m, *args, &block)
|
60
59
|
case m
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
60
|
+
when /^test_/
|
61
|
+
"# #{m} is not currently defined. Stay tuned for updates."
|
62
|
+
else
|
63
|
+
raise NoMethodError
|
65
64
|
end
|
66
65
|
end
|
67
66
|
|
@@ -82,14 +81,12 @@ module Umami
|
|
82
81
|
enforce_styling(test_root)
|
83
82
|
|
84
83
|
unless test_files_written.empty?
|
85
|
-
puts
|
84
|
+
puts 'Wrote the following integration tests:'
|
86
85
|
test_files_written.each do |f|
|
87
86
|
puts "\t#{f}"
|
88
87
|
end
|
89
88
|
end
|
90
|
-
|
91
89
|
end
|
92
|
-
|
93
90
|
end
|
94
91
|
end
|
95
92
|
end
|
data/lib/chef-umami/test/unit.rb
CHANGED
@@ -19,20 +19,19 @@ require 'chef-umami/helpers/filetools'
|
|
19
19
|
module Umami
|
20
20
|
class Test
|
21
21
|
class Unit < Umami::Test
|
22
|
-
|
23
22
|
include Umami::Helper::OS
|
24
23
|
include Umami::Helper::FileTools
|
25
24
|
|
26
25
|
attr_reader :test_root
|
27
26
|
attr_reader :tested_cookbook # This cookbook.
|
28
|
-
def initialize
|
27
|
+
def initialize(root_dir)
|
29
28
|
super
|
30
29
|
@test_root = File.join(self.root_dir, 'umami', 'unit', 'recipes')
|
31
30
|
@tested_cookbook = File.basename(Dir.pwd)
|
32
31
|
end
|
33
32
|
|
34
33
|
def framework
|
35
|
-
|
34
|
+
'chefspec'
|
36
35
|
end
|
37
36
|
|
38
37
|
def test_file(recipe = '')
|
@@ -62,7 +61,7 @@ module Umami
|
|
62
61
|
def write_test(resource = nil)
|
63
62
|
state_attrs = [] # Attribute hash to be used with #with()
|
64
63
|
resource.state.each do |attr, value|
|
65
|
-
next if value.nil?
|
64
|
+
next if value.nil? || (value.respond_to?(:empty) && value.empty?)
|
66
65
|
if value.is_a? String
|
67
66
|
value = value.gsub("'", "\\\\'") # Escape any single quotes in the value.
|
68
67
|
end
|
@@ -95,7 +94,7 @@ module Umami
|
|
95
94
|
resources.each do |resource|
|
96
95
|
content << write_test(resource)
|
97
96
|
end
|
98
|
-
content <<
|
97
|
+
content << 'end'
|
99
98
|
test_file_name = test_file(recipe)
|
100
99
|
test_file_content = content.join("\n") + "\n"
|
101
100
|
write_file(test_file_name, test_file_content)
|
@@ -107,14 +106,12 @@ module Umami
|
|
107
106
|
test_files_written << spec_helper_path
|
108
107
|
|
109
108
|
unless test_files_written.empty?
|
110
|
-
puts
|
109
|
+
puts 'Wrote the following unit test files:'
|
111
110
|
test_files_written.each do |f|
|
112
111
|
puts "\t#{f}"
|
113
112
|
end
|
114
113
|
end
|
115
|
-
|
116
114
|
end
|
117
|
-
|
118
115
|
end
|
119
116
|
end
|
120
117
|
end
|
data/lib/chef-umami/version.rb
CHANGED
data/spec/runner_spec.rb
CHANGED
@@ -12,10 +12,10 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
-
require
|
15
|
+
require 'chef-umami/runner'
|
16
16
|
|
17
17
|
RSpec.describe Umami::Runner do
|
18
|
-
it
|
18
|
+
it 'initiates a Umami::Runner object' do
|
19
19
|
runner = Umami::Runner.new
|
20
20
|
expect(runner).to be_an_instance_of(Umami::Runner)
|
21
21
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,103 +0,0 @@
|
|
1
|
-
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
-
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
-
# The generated `.rspec` file contains `--require spec_helper` which will cause
|
4
|
-
# this file to always be loaded, without a need to explicitly require it in any
|
5
|
-
# files.
|
6
|
-
#
|
7
|
-
# Given that it is always loaded, you are encouraged to keep this file as
|
8
|
-
# light-weight as possible. Requiring heavyweight dependencies from this file
|
9
|
-
# will add to the boot time of your test suite on EVERY test run, even for an
|
10
|
-
# individual file that may not need all of that loaded. Instead, consider making
|
11
|
-
# a separate helper file that requires the additional dependencies and performs
|
12
|
-
# the additional setup, and require it from the spec files that actually need
|
13
|
-
# it.
|
14
|
-
#
|
15
|
-
# The `.rspec` file also contains a few flags that are not defaults but that
|
16
|
-
# users commonly want.
|
17
|
-
#
|
18
|
-
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
19
|
-
RSpec.configure do |config|
|
20
|
-
# rspec-expectations config goes here. You can use an alternate
|
21
|
-
# assertion/expectation library such as wrong or the stdlib/minitest
|
22
|
-
# assertions if you prefer.
|
23
|
-
config.expect_with :rspec do |expectations|
|
24
|
-
# This option will default to `true` in RSpec 4. It makes the `description`
|
25
|
-
# and `failure_message` of custom matchers include text for helper methods
|
26
|
-
# defined using `chain`, e.g.:
|
27
|
-
# be_bigger_than(2).and_smaller_than(4).description
|
28
|
-
# # => "be bigger than 2 and smaller than 4"
|
29
|
-
# ...rather than:
|
30
|
-
# # => "be bigger than 2"
|
31
|
-
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
32
|
-
end
|
33
|
-
|
34
|
-
# rspec-mocks config goes here. You can use an alternate test double
|
35
|
-
# library (such as bogus or mocha) by changing the `mock_with` option here.
|
36
|
-
config.mock_with :rspec do |mocks|
|
37
|
-
# Prevents you from mocking or stubbing a method that does not exist on
|
38
|
-
# a real object. This is generally recommended, and will default to
|
39
|
-
# `true` in RSpec 4.
|
40
|
-
mocks.verify_partial_doubles = true
|
41
|
-
end
|
42
|
-
|
43
|
-
# This option will default to `:apply_to_host_groups` in RSpec 4 (and will
|
44
|
-
# have no way to turn it off -- the option exists only for backwards
|
45
|
-
# compatibility in RSpec 3). It causes shared context metadata to be
|
46
|
-
# inherited by the metadata hash of host groups and examples, rather than
|
47
|
-
# triggering implicit auto-inclusion in groups with matching metadata.
|
48
|
-
config.shared_context_metadata_behavior = :apply_to_host_groups
|
49
|
-
|
50
|
-
# The settings below are suggested to provide a good initial experience
|
51
|
-
# with RSpec, but feel free to customize to your heart's content.
|
52
|
-
=begin
|
53
|
-
# This allows you to limit a spec run to individual examples or groups
|
54
|
-
# you care about by tagging them with `:focus` metadata. When nothing
|
55
|
-
# is tagged with `:focus`, all examples get run. RSpec also provides
|
56
|
-
# aliases for `it`, `describe`, and `context` that include `:focus`
|
57
|
-
# metadata: `fit`, `fdescribe` and `fcontext`, respectively.
|
58
|
-
config.filter_run_when_matching :focus
|
59
|
-
|
60
|
-
# Allows RSpec to persist some state between runs in order to support
|
61
|
-
# the `--only-failures` and `--next-failure` CLI options. We recommend
|
62
|
-
# you configure your source control system to ignore this file.
|
63
|
-
config.example_status_persistence_file_path = "spec/examples.txt"
|
64
|
-
|
65
|
-
# Limits the available syntax to the non-monkey patched syntax that is
|
66
|
-
# recommended. For more details, see:
|
67
|
-
# - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
|
68
|
-
# - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
|
69
|
-
# - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
|
70
|
-
config.disable_monkey_patching!
|
71
|
-
|
72
|
-
# This setting enables warnings. It's recommended, but in some cases may
|
73
|
-
# be too noisy due to issues in dependencies.
|
74
|
-
config.warnings = true
|
75
|
-
|
76
|
-
# Many RSpec users commonly either run the entire suite or an individual
|
77
|
-
# file, and it's useful to allow more verbose output when running an
|
78
|
-
# individual spec file.
|
79
|
-
if config.files_to_run.one?
|
80
|
-
# Use the documentation formatter for detailed output,
|
81
|
-
# unless a formatter has already been configured
|
82
|
-
# (e.g. via a command-line flag).
|
83
|
-
config.default_formatter = 'doc'
|
84
|
-
end
|
85
|
-
|
86
|
-
# Print the 10 slowest examples and example groups at the
|
87
|
-
# end of the spec run, to help surface which specs are running
|
88
|
-
# particularly slow.
|
89
|
-
config.profile_examples = 10
|
90
|
-
|
91
|
-
# Run specs in random order to surface order dependencies. If you find an
|
92
|
-
# order dependency and want to debug it, you can fix the order by providing
|
93
|
-
# the seed, which is printed after each run.
|
94
|
-
# --seed 1234
|
95
|
-
config.order = :random
|
96
|
-
|
97
|
-
# Seed global randomization in this process using the `--seed` CLI option.
|
98
|
-
# Setting this allows you to use `--seed` to deterministically reproduce
|
99
|
-
# test failures related to randomization by passing the same `--seed` value
|
100
|
-
# as the one that triggered the failure.
|
101
|
-
Kernel.srand config.seed
|
102
|
-
=end
|
103
|
-
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: chef-umami
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Frantz
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-11-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: chef
|
@@ -74,6 +74,7 @@ files:
|
|
74
74
|
- lib/chef-umami/helpers/inspec.rb
|
75
75
|
- lib/chef-umami/helpers/os.rb
|
76
76
|
- lib/chef-umami/logger.rb
|
77
|
+
- lib/chef-umami/options.rb
|
77
78
|
- lib/chef-umami/policyfile.rb
|
78
79
|
- lib/chef-umami/policyfile/exporter.rb
|
79
80
|
- lib/chef-umami/policyfile/uploader.rb
|