rspec-puppet-augeas 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/Gemfile +3 -0
- data/LICENSE +20 -0
- data/README.md +122 -0
- data/Rakefile +7 -0
- data/lib/rspec-puppet-augeas.rb +15 -0
- data/lib/rspec-puppet-augeas/example.rb +6 -0
- data/lib/rspec-puppet-augeas/example/run_augeas_example_group.rb +65 -0
- data/lib/rspec-puppet-augeas/fixtures.rb +59 -0
- data/lib/rspec-puppet-augeas/matchers.rb +5 -0
- data/lib/rspec-puppet-augeas/matchers/execute.rb +70 -0
- data/lib/rspec-puppet-augeas/resource.rb +45 -0
- data/lib/rspec-puppet-augeas/test_utils.rb +148 -0
- data/rspec-puppet-augeas.gemspec +35 -0
- data/spec/classes/sshd_config_spec.rb +58 -0
- data/spec/fixtures/augeas/etc/ssh/sshd_config +138 -0
- data/spec/fixtures/augeas/etc/ssh/sshd_config_2 +2 -0
- data/spec/fixtures/manifests/site.pp +0 -0
- data/spec/fixtures/modules/sshd/manifests/init.pp +19 -0
- data/spec/spec_helper.rb +7 -0
- metadata +80 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2013 Dominic Cleal
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
17
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
18
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
19
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
20
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
# RSpec tests for Augeas resources inside Puppet manifests
|
2
|
+
|
3
|
+
## Summary
|
4
|
+
|
5
|
+
rspec-puppet-augeas is an extension of rodjek's popular rspec-puppet tool. It
|
6
|
+
adds to your RSpec tests for a single class or define (or anything resulting in
|
7
|
+
a catalog) and allows you to run and test individual Augeas resources within it.
|
8
|
+
|
9
|
+
It takes a set of input files (fixtures) that the resource(s) will modify, runs
|
10
|
+
the resource, can verify it changed and is idempotent, then provides
|
11
|
+
Augeas-based tools to help verify the modification was made.
|
12
|
+
|
13
|
+
## Setting up
|
14
|
+
|
15
|
+
Install the gem first:
|
16
|
+
|
17
|
+
gem install rspec-puppet-augeas
|
18
|
+
|
19
|
+
Extend your usual rspec-puppet class test, e.g. for the 'sshd' class:
|
20
|
+
|
21
|
+
$ cat spec/classes/sshd_config_spec.rb
|
22
|
+
describe 'sshd' do
|
23
|
+
it 'should have an augeas resource' do
|
24
|
+
should contain_augeas('root login')
|
25
|
+
end
|
26
|
+
|
27
|
+
# Expects Augeas['root login']
|
28
|
+
describe_augeas 'root login' do
|
29
|
+
it 'should change PermitRootLogin' do
|
30
|
+
# Run the resource against the fixtures, check it changed
|
31
|
+
should execute.with_change
|
32
|
+
|
33
|
+
# Check changes in the file with aug_get and aug_match
|
34
|
+
aug_get('PermitRootLogin').should == 'no'
|
35
|
+
|
36
|
+
# Verify idempotence last to prevent false positive
|
37
|
+
should execute.idempotently
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
Copy original input files to `spec/fixtures/augeas` using the same filesystem
|
43
|
+
layout that the resource expects:
|
44
|
+
|
45
|
+
$ tree spec/fixtures/augeas/
|
46
|
+
spec/fixtures/augeas/
|
47
|
+
`-- etc
|
48
|
+
`-- ssh
|
49
|
+
`-- sshd_config
|
50
|
+
|
51
|
+
|
52
|
+
Lastly, in your `spec/spec_helper.rb`, load ruby-puppet-augeas and configure the
|
53
|
+
fixtures directory.
|
54
|
+
|
55
|
+
require 'rspec-puppet-augeas'
|
56
|
+
RSpec.configure do |c|
|
57
|
+
c.augeas_fixtures = File.join(File.dirname(File.expand_path(__FILE__)), 'fixtures', 'augeas')
|
58
|
+
end
|
59
|
+
|
60
|
+
## Usage
|
61
|
+
|
62
|
+
### describe\_augeas example group
|
63
|
+
|
64
|
+
`describe_augeas` adds an example group, like describe/context, but that describes
|
65
|
+
an Augeas resource from the catalog. The description given to run\_augeas must
|
66
|
+
match the resource title. `run_augeas` is a synonym.
|
67
|
+
|
68
|
+
It takes optional hash arguments:
|
69
|
+
|
70
|
+
* `:fixtures` manages the files to run the resource against
|
71
|
+
* a hash of fixtures to copy from the source (under augeas\_fixtures) to a
|
72
|
+
destination path, e.g. `{ 'dest/file/location' => 'src/file/location' }`
|
73
|
+
* a string of the source path, copied to the path given by the resource's
|
74
|
+
`incl` parameter (TODO: or `:target`)
|
75
|
+
* nil, by default copies all fixtures
|
76
|
+
* `:target` is the path of the file that the resource should modify
|
77
|
+
* `:lens` is the lens to use when opening the target file (for `aug_*` etc.)
|
78
|
+
|
79
|
+
It sets the following variables inside examples:
|
80
|
+
|
81
|
+
* `subject` (used implicitly) to an object representing the resource
|
82
|
+
* `output_root` to the path of the fixtures directory after one run
|
83
|
+
|
84
|
+
### execute matcher
|
85
|
+
|
86
|
+
The `execute` matcher is used to check how a resource has run, e.g.
|
87
|
+
|
88
|
+
subject.should execute
|
89
|
+
|
90
|
+
(subject is implicit and so can be left out)
|
91
|
+
|
92
|
+
It has methods to add to the checks it performs:
|
93
|
+
|
94
|
+
* `with_change` ensures the resource was "applied" and didn't no-op
|
95
|
+
* `idempotently` runs the resource again to ensure it only applies once
|
96
|
+
|
97
|
+
### Test utilities
|
98
|
+
|
99
|
+
Helpers are provided to check the modifications made to files after applying
|
100
|
+
the resource. Some require certain options, which can be supplied in the
|
101
|
+
`describe_augeas` statement or as arguments to the method.
|
102
|
+
|
103
|
+
* `output_root` returns the root directory of the modified fixtures
|
104
|
+
* `open_target` opens the target file and returns the handle, closes it too if
|
105
|
+
given a block (expects `:target` option)
|
106
|
+
* `aug_open` opens the target file and returns an Augeas object, closes it too
|
107
|
+
if given a block (expects `:target` and `:lens`)
|
108
|
+
* `aug_get(path)` runs `Augeas#get(path)` against the target file (expects
|
109
|
+
`:target` and `:lens`), returns the value of the node
|
110
|
+
* `aug_match(path)` runs `Augeas#match(path)` against the target file (expects
|
111
|
+
`:target` and `:lens`), returns an array of matches
|
112
|
+
|
113
|
+
### RSpec configuration
|
114
|
+
|
115
|
+
New RSpec configuration options:
|
116
|
+
|
117
|
+
* `augeas_fixtures` is the path to the root of the fixtures directory
|
118
|
+
containing source files
|
119
|
+
|
120
|
+
## Issues
|
121
|
+
|
122
|
+
Please file any issues or suggestions [on GitHub](https://github.com/domcleal/rspec-puppet-augeas/issues).
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'rspec-puppet'
|
2
|
+
|
3
|
+
module RSpec::Puppet::Augeas
|
4
|
+
class Error < StandardError
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
require 'rspec-puppet-augeas/example'
|
9
|
+
require 'rspec-puppet-augeas/matchers'
|
10
|
+
require 'rspec-puppet-augeas/test_utils'
|
11
|
+
|
12
|
+
RSpec.configure do |c|
|
13
|
+
c.add_setting :augeas_fixtures, :default => nil
|
14
|
+
c.include RSpec::Puppet::Augeas::TestUtils, :type => :augeas
|
15
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'rspec-puppet-augeas/resource'
|
2
|
+
|
3
|
+
module RSpec::Puppet::Augeas
|
4
|
+
module RunAugeasExampleGroup
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
# new example group, much like 'describe'
|
8
|
+
# title (arg #1) must match title of Augeas resource
|
9
|
+
# args may be hash containing:
|
10
|
+
# :fixture =>
|
11
|
+
# String -> relative path of source fixture file
|
12
|
+
# Hash -> { "/dest/path" => "source/fixture/path", ... }
|
13
|
+
# :target => path of destination file to be modified
|
14
|
+
# :lens => lens used for opening target
|
15
|
+
def run_augeas(*args, &block)
|
16
|
+
options = args.last.is_a?(::Hash) ? args.pop : {}
|
17
|
+
|
18
|
+
title = "Augeas[#{args.shift}]"
|
19
|
+
describe(title, *args, :type => :augeas) do
|
20
|
+
# inside here (the type augeas block), subject will be initialised
|
21
|
+
# to the augeas resource object
|
22
|
+
|
23
|
+
# initialise arguments passed into the run_augeas block
|
24
|
+
# TODO: target can be initialised from incl if available
|
25
|
+
target = options.delete(:target)
|
26
|
+
let(:target) { target }
|
27
|
+
|
28
|
+
# TODO: ditto
|
29
|
+
lens = options.delete(:lens)
|
30
|
+
let(:lens) { lens }
|
31
|
+
|
32
|
+
fixture = options.delete(:fixture)
|
33
|
+
fixture = { target => fixture } if fixture.is_a? String and target
|
34
|
+
let(:fixture) { fixture }
|
35
|
+
|
36
|
+
class_exec(&block)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Synonym for run_augeas
|
41
|
+
def describe_augeas(*args, &block)
|
42
|
+
run_augeas(*args, &block)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
module InstanceMethods
|
47
|
+
# Initialises the implicit example group 'subject' to an Augeas resource
|
48
|
+
#
|
49
|
+
# Requires that the title of this example group is the resource title and
|
50
|
+
# that the parent example group subject is a catalog (use rspec-puppet)
|
51
|
+
def subject
|
52
|
+
unless @resource
|
53
|
+
title = self.class.description
|
54
|
+
title = $1 if title =~ /^Augeas\[(.*)\]$/
|
55
|
+
@resource = Resource.new(catalogue.resource('Augeas', title), fixture)
|
56
|
+
end
|
57
|
+
@resource
|
58
|
+
end
|
59
|
+
|
60
|
+
def output_root
|
61
|
+
subject.root
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'augeas'
|
2
|
+
require 'tempfile'
|
3
|
+
require 'tmpdir'
|
4
|
+
|
5
|
+
module RSpec::Puppet::Augeas
|
6
|
+
module Fixtures
|
7
|
+
# Copies test fixtures to a temporary directory
|
8
|
+
# If file is nil, copies the entire augeas_fixtures directory
|
9
|
+
# If file is a string, it copies that file from augeas_fixtures
|
10
|
+
# to the path being edited by the resource
|
11
|
+
# If file is a hash, it copies the "value" from augeas_fixtures
|
12
|
+
# to each "key" path
|
13
|
+
def load_fixtures(resource, file)
|
14
|
+
if block_given?
|
15
|
+
Dir.mktmpdir("rspec-puppet-augeas") do |dir|
|
16
|
+
prepare_fixtures(dir, resource, file)
|
17
|
+
yield dir
|
18
|
+
end
|
19
|
+
else
|
20
|
+
dir = Dir.mktmpdir("rspec-puppet-augeas")
|
21
|
+
prepare_fixtures(dir, resource, file)
|
22
|
+
dir
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def prepare_fixtures(dir, resource, file)
|
27
|
+
if file.nil?
|
28
|
+
FileUtils.cp_r File.join(RSpec.configuration.augeas_fixtures, "."), dir
|
29
|
+
elsif file.is_a? Hash
|
30
|
+
file.each do |dest,src|
|
31
|
+
FileUtils.mkdir_p File.join(dir, File.dirname(dest))
|
32
|
+
src = File.join(RSpec.configuration.augeas_fixtures, src) unless src.start_with? File::SEPARATOR
|
33
|
+
FileUtils.cp_r src, File.join(dir, dest)
|
34
|
+
end
|
35
|
+
elsif file.respond_to? :to_s
|
36
|
+
target = get_resource_target(resource)
|
37
|
+
raise ArgumentError, "Unable to determine file being edited from #{resource.name}. Supply :fixtures as a hash of { '/dest/path' => 'source/fixture/path' } instead." unless target
|
38
|
+
FileUtils.mkdir_p File.join(dir, File.dirname(target))
|
39
|
+
FileUtils.cp File.join(RSpec.configuration.augeas_fixtures, file.to_s), File.join(dir, target)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Take a best guess at the file the user's editing
|
44
|
+
def get_resource_target(resource)
|
45
|
+
return resource[:incl] if resource[:incl]
|
46
|
+
# TODO: make reliable
|
47
|
+
#return $1 if resource[:context] and resource[:context] =~ %r{/files(/.*)}
|
48
|
+
nil
|
49
|
+
end
|
50
|
+
|
51
|
+
# Runs a particular resource via a catalog
|
52
|
+
def apply(resource)
|
53
|
+
catalog = Puppet::Resource::Catalog.new
|
54
|
+
catalog.add_resource resource
|
55
|
+
catalog = catalog.to_ral if resource.is_a? Puppet::Resource
|
56
|
+
catalog.apply
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'rspec-puppet-augeas/fixtures'
|
2
|
+
|
3
|
+
module RSpec::Puppet::Augeas::Matchers
|
4
|
+
# <subject>.should execute()
|
5
|
+
# where subject must be an Augeas resource
|
6
|
+
class Execute
|
7
|
+
attr_reader :resource, :idempotent, :change
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@change = false
|
11
|
+
@idempotent = false
|
12
|
+
end
|
13
|
+
|
14
|
+
def matches?(resource)
|
15
|
+
@resource = resource
|
16
|
+
return false if resource.txn.any_failed?
|
17
|
+
return false if change and !resource.txn.changed?.any?
|
18
|
+
return false if idempotent and resource.idempotent.changed?.any?
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
22
|
+
# verifies the resource was 'applied'
|
23
|
+
def with_change
|
24
|
+
@change = true
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
# verifies the resource only applies once
|
29
|
+
def idempotently
|
30
|
+
@change = true
|
31
|
+
@idempotent = true
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
def description
|
36
|
+
if idempotent
|
37
|
+
"should change once only (idempotently)"
|
38
|
+
elsif change
|
39
|
+
"should change successfully at least once"
|
40
|
+
else
|
41
|
+
"should execute without failure"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def failure_message_for_should
|
46
|
+
# FIXME: the branch should depend on outcome, not chaining
|
47
|
+
if idempotent
|
48
|
+
"#{resource} isn't idempotent, it changes on every run"
|
49
|
+
elsif change
|
50
|
+
"#{resource} doesn't change when executed"
|
51
|
+
else
|
52
|
+
"#{resource} fails when executed"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def failure_message_for_should_not
|
57
|
+
if idempotent
|
58
|
+
"#{resource} is idempotent, it doesn't change on every run"
|
59
|
+
elsif change
|
60
|
+
"#{resource} changes when executed"
|
61
|
+
else
|
62
|
+
"#{resource} succeeds when executed"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def execute
|
68
|
+
Execute.new
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'rspec-puppet-augeas/fixtures'
|
2
|
+
|
3
|
+
module RSpec::Puppet::Augeas
|
4
|
+
class Resource
|
5
|
+
attr_reader :resource, :txn, :txn_idempotent, :root
|
6
|
+
|
7
|
+
def initialize(resource, fixtures)
|
8
|
+
@resource = resource
|
9
|
+
|
10
|
+
# The directory where the resource has run will be valuable, so keep it
|
11
|
+
# for analysis and tests by the user
|
12
|
+
@root = load_fixtures(resource, fixtures)
|
13
|
+
ObjectSpace.define_finalizer(self, self.class.finalize(@root))
|
14
|
+
|
15
|
+
resource[:root] = @root
|
16
|
+
@txn = apply(resource)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.finalize(root)
|
20
|
+
proc { FileUtils.rm_rf root }
|
21
|
+
end
|
22
|
+
|
23
|
+
# Run the resource a second time, against the output dir from the first
|
24
|
+
#
|
25
|
+
# @return [Puppet::Transaction] repeated transaction
|
26
|
+
def idempotent
|
27
|
+
root = load_fixtures(resource, {"." => "#{@root}/."})
|
28
|
+
|
29
|
+
oldroot = resource[:root]
|
30
|
+
resource[:root] = root
|
31
|
+
@txn_idempotent = apply(resource)
|
32
|
+
FileUtils.rm_r root
|
33
|
+
resource[:root] = oldroot
|
34
|
+
|
35
|
+
@txn_idempotent
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_s
|
39
|
+
resource.to_s
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
include RSpec::Puppet::Augeas::Fixtures
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
require 'augeas'
|
2
|
+
require 'tempfile'
|
3
|
+
|
4
|
+
module RSpec::Puppet::Augeas
|
5
|
+
module TestUtils
|
6
|
+
def open_target(opts = {})
|
7
|
+
file = opts[:target] || self.target or raise ArgumentError, ":target must be supplied"
|
8
|
+
f = File.open(File.join(self.output_root, file))
|
9
|
+
return f unless block_given?
|
10
|
+
yield f
|
11
|
+
f.close
|
12
|
+
end
|
13
|
+
|
14
|
+
def aug_get(path, opts = {})
|
15
|
+
aug_open(opts) do |aug|
|
16
|
+
aug.get(path)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def aug_match(path, opts = {})
|
21
|
+
aug_open(opts) do |aug|
|
22
|
+
aug.match(path)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Open Augeas on a given file, by default the target / lens specified in
|
27
|
+
# options to the run_augeas block
|
28
|
+
def aug_open(opts = {})
|
29
|
+
file = opts[:target] || self.target or raise ArgumentError, ":target must be supplied"
|
30
|
+
file = "/#{file}" unless file.start_with? '/'
|
31
|
+
lens = opts[:lens] || self.lens or raise ArgumentError, ":lens must be supplied"
|
32
|
+
lens = "#{lens}.lns" unless lens.include? '.'
|
33
|
+
|
34
|
+
aug = Augeas.open(self.output_root, nil, Augeas::NO_MODL_AUTOLOAD)
|
35
|
+
begin
|
36
|
+
aug.transform(
|
37
|
+
:lens => lens,
|
38
|
+
:name => lens.split(".")[0],
|
39
|
+
:incl => file
|
40
|
+
)
|
41
|
+
aug.set("/augeas/context", "/files#{file}")
|
42
|
+
aug.load!
|
43
|
+
raise RSpec::Puppet::Augeas::Error, "Augeas didn't load #{file}" if aug.match(".").empty?
|
44
|
+
yield aug
|
45
|
+
rescue Augeas::Error
|
46
|
+
errors = []
|
47
|
+
aug.match("/augeas//error").each do |errnode|
|
48
|
+
aug.match("#{errnode}/*").each do |subnode|
|
49
|
+
subvalue = aug.get(subnode)
|
50
|
+
errors << "#{subnode} = #{subvalue}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
raise RSpec::Puppet::Augeas::Error, errors.join("\n")
|
54
|
+
ensure
|
55
|
+
aug.close
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Creates a simple test file, reads in a fixture (that's been modified by
|
60
|
+
# the provider) and runs augparse against the expected tree.
|
61
|
+
def augparse(opts = {})
|
62
|
+
file = opts[:target] || self.target or raise ArgumentError, ":target must be supplied"
|
63
|
+
file = File.join(self.output_path, file) unless file.start_with? '/'
|
64
|
+
lens = opts[:lens] || self.lens or raise ArgumentError, ":lens must be supplied"
|
65
|
+
lens = "#{lens}.lns" unless lens.include? '.'
|
66
|
+
result = opts[:result] || "?"
|
67
|
+
|
68
|
+
Dir.mktmpdir { |dir|
|
69
|
+
# Augeas always starts with a blank line when creating new files, so
|
70
|
+
# reprocess file and remove it to make writing tests easier
|
71
|
+
File.open("#{dir}/input", "w") do |finput|
|
72
|
+
File.open(file, "r") do |ffile|
|
73
|
+
line = ffile.readline
|
74
|
+
finput.write line unless line == "\n"
|
75
|
+
ffile.each {|line| finput.write line }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Test module, Augeas reads back in the input file
|
80
|
+
testaug = "#{dir}/test_augeasproviders.aug"
|
81
|
+
File.open(testaug, "w") { |tf|
|
82
|
+
tf.write(<<eos)
|
83
|
+
module Test_Augeasproviders =
|
84
|
+
test #{lens} get Sys.read_file "#{dir}/input" =
|
85
|
+
#{result}
|
86
|
+
eos
|
87
|
+
}
|
88
|
+
|
89
|
+
output = %x(augparse #{testaug} 2>&1)
|
90
|
+
raise RSpec::Puppet::Augeas::Error, "augparse failed:\n#{output}" unless $? == 0 && output.empty?
|
91
|
+
}
|
92
|
+
end
|
93
|
+
|
94
|
+
# Takes a full fixture file, loads it in Augeas, uses the relative path
|
95
|
+
# and/or filter and saves just that part in a new file. That's then passed
|
96
|
+
# into augparse and compared against the expected tree. Saves creating a
|
97
|
+
# tree of the entire file.
|
98
|
+
#
|
99
|
+
# Because the filtered fragment is saved in a new file, seq labels will reset
|
100
|
+
# too, so it'll be "1" rather than what it was in the original fixture.
|
101
|
+
def augparse_filter(opts = {})
|
102
|
+
file = opts[:target] || self.target or raise ArgumentError, ":target must be supplied"
|
103
|
+
file = File.join(self.output_path, file) unless file.start_with? '/'
|
104
|
+
lens = opts[:lens] || self.lens or raise ArgumentError, ":lens must be supplied"
|
105
|
+
lens = "#{lens}.lns" unless lens.include? '.'
|
106
|
+
filter = opts[:filter] or raise ArgumentError, ":filter must be supplied"
|
107
|
+
result = opts[:result] || "?"
|
108
|
+
|
109
|
+
# duplicate the original since we use aug.mv
|
110
|
+
tmpin = Tempfile.new("original")
|
111
|
+
tmpin.write(File.read(file))
|
112
|
+
tmpin.close
|
113
|
+
|
114
|
+
tmpout = Tempfile.new("filtered")
|
115
|
+
tmpout.close
|
116
|
+
|
117
|
+
aug_open(tmpin.path, lens) do |aug|
|
118
|
+
# Load a transform of the target, so Augeas can write into it
|
119
|
+
aug.transform(
|
120
|
+
:lens => lens,
|
121
|
+
:name => lens.split(".")[0],
|
122
|
+
:incl => tmpout.path
|
123
|
+
)
|
124
|
+
aug.load!
|
125
|
+
tmpaug = "/files#{tmpout.path}"
|
126
|
+
raise RSpec::Puppet::Augeas::Error, "Augeas didn't load empty file #{tmpout.path}" if aug.match(tmpaug).empty?
|
127
|
+
|
128
|
+
# Check the filter matches something and move it
|
129
|
+
ftmatch = aug.match(filter)
|
130
|
+
raise RSpec::Puppet::Augeas::Error, "Filter #{filter} within #{file} matched #{ftmatch.size} nodes, should match at least one" if ftmatch.empty?
|
131
|
+
|
132
|
+
begin
|
133
|
+
# Loop on aug_match as path indexes will change as we move nodes
|
134
|
+
fp = ftmatch.first
|
135
|
+
aug.mv(fp, "#{tmpaug}/#{fp.split(/\//)[-1]}")
|
136
|
+
ftmatch = aug.match(filter)
|
137
|
+
end while not ftmatch.empty?
|
138
|
+
|
139
|
+
aug.save!
|
140
|
+
end
|
141
|
+
|
142
|
+
augparse(tmpout.path, lens, result)
|
143
|
+
ensure
|
144
|
+
tmpin.unlink
|
145
|
+
tmpout.unlink
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'rspec-puppet-augeas'
|
3
|
+
s.version = '0.1.0'
|
4
|
+
s.homepage = 'https://github.com/domcleal/rspec-puppet-augeas/'
|
5
|
+
s.summary = 'RSpec tests for Augeas resources in Puppet manifests'
|
6
|
+
s.description = 'RSpec tests for Augeas resources in Puppet manifests'
|
7
|
+
|
8
|
+
s.files = [
|
9
|
+
'.gitignore',
|
10
|
+
'Gemfile',
|
11
|
+
'LICENSE',
|
12
|
+
'README.md',
|
13
|
+
'Rakefile',
|
14
|
+
'lib/rspec-puppet-augeas.rb',
|
15
|
+
'lib/rspec-puppet-augeas/example.rb',
|
16
|
+
'lib/rspec-puppet-augeas/example/run_augeas_example_group.rb',
|
17
|
+
'lib/rspec-puppet-augeas/fixtures.rb',
|
18
|
+
'lib/rspec-puppet-augeas/matchers.rb',
|
19
|
+
'lib/rspec-puppet-augeas/matchers/execute.rb',
|
20
|
+
'lib/rspec-puppet-augeas/resource.rb',
|
21
|
+
'lib/rspec-puppet-augeas/test_utils.rb',
|
22
|
+
'rspec-puppet-augeas.gemspec',
|
23
|
+
'spec/classes/sshd_config_spec.rb',
|
24
|
+
'spec/fixtures/augeas/etc/ssh/sshd_config',
|
25
|
+
'spec/fixtures/augeas/etc/ssh/sshd_config_2',
|
26
|
+
'spec/fixtures/manifests/site.pp',
|
27
|
+
'spec/fixtures/modules/sshd/manifests/init.pp',
|
28
|
+
'spec/spec_helper.rb'
|
29
|
+
]
|
30
|
+
|
31
|
+
s.add_dependency 'rspec-puppet'
|
32
|
+
|
33
|
+
s.authors = ['Dominic Cleal']
|
34
|
+
s.email = 'dcleal@redhat.com'
|
35
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'sshd' do
|
4
|
+
it 'should have an augeas resource' do
|
5
|
+
should contain_augeas('root login')
|
6
|
+
end
|
7
|
+
|
8
|
+
describe 'specify target+lens upfront, use all fixtures' do
|
9
|
+
describe_augeas 'root login', :lens => 'Sshd', :target => 'etc/ssh/sshd_config' do
|
10
|
+
it 'should test resource' do
|
11
|
+
aug_get('#comment[1]').should =~ /OpenBSD/
|
12
|
+
open_target { |f| f.readline.should =~ /OpenBSD/ }
|
13
|
+
|
14
|
+
should execute.with_change
|
15
|
+
aug_get('PermitRootLogin').should == 'yes'
|
16
|
+
open_target { |f| f.read.should =~ /^PermitRootLogin\s+yes$/ }
|
17
|
+
|
18
|
+
should execute.idempotently
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe 'specify fixtures as a hash' do
|
24
|
+
describe_augeas 'root login', :fixture => { 'etc/ssh/sshd_config' => 'etc/ssh/sshd_config_2' } do
|
25
|
+
it 'should test resource with second fixture' do
|
26
|
+
aug_get('#comment[1]', :lens => 'Sshd', :target => 'etc/ssh/sshd_config').should == 'Fixture 2'
|
27
|
+
should execute.with_change
|
28
|
+
aug_get('PermitRootLogin', :lens => 'Sshd', :target => 'etc/ssh/sshd_config').should == 'yes'
|
29
|
+
should execute.idempotently
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe 'specify target and non-standard fixture' do
|
35
|
+
describe_augeas 'root login', :lens => 'Sshd', :target => 'etc/ssh/sshd_config', :fixture => 'etc/ssh/sshd_config_2' do
|
36
|
+
it 'should test resource with second fixture' do
|
37
|
+
aug_get('#comment[1]').should == 'Fixture 2'
|
38
|
+
should execute.with_change
|
39
|
+
aug_get('PermitRootLogin').should == 'yes'
|
40
|
+
should execute.idempotently
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe_augeas 'fail to add root login' do
|
46
|
+
it 'should fail to run entirely' do
|
47
|
+
should_not execute
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
run_augeas 'add root login', :lens => 'Sshd', :target => 'etc/ssh/sshd_config' do
|
52
|
+
it 'should fail on idempotency' do
|
53
|
+
should execute.with_change
|
54
|
+
aug_match('PermitRootLogin').size.should == 2
|
55
|
+
should_not execute.idempotently
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
# $OpenBSD: sshd_config,v 1.81 2009/10/08 14:03:41 markus Exp $
|
2
|
+
|
3
|
+
# This is the sshd server system-wide configuration file. See
|
4
|
+
# sshd_config(5) for more information.
|
5
|
+
|
6
|
+
# This sshd was compiled with PATH=/usr/local/bin:/bin:/usr/bin
|
7
|
+
|
8
|
+
# The strategy used for options in the default sshd_config shipped with
|
9
|
+
# OpenSSH is to specify options with their default value where
|
10
|
+
# possible, but leave them commented. Uncommented options change a
|
11
|
+
# default value.
|
12
|
+
|
13
|
+
#Port 22
|
14
|
+
#AddressFamily any
|
15
|
+
#ListenAddress 0.0.0.0
|
16
|
+
#ListenAddress ::
|
17
|
+
|
18
|
+
# The default requires explicit activation of protocol 1
|
19
|
+
#Protocol 2
|
20
|
+
|
21
|
+
# HostKey for protocol version 1
|
22
|
+
#HostKey /etc/ssh/ssh_host_key
|
23
|
+
# HostKeys for protocol version 2
|
24
|
+
#HostKey /etc/ssh/ssh_host_rsa_key
|
25
|
+
#HostKey /etc/ssh/ssh_host_dsa_key
|
26
|
+
|
27
|
+
# Lifetime and size of ephemeral version 1 server key
|
28
|
+
#KeyRegenerationInterval 1h
|
29
|
+
#ServerKeyBits 1024
|
30
|
+
|
31
|
+
# Logging
|
32
|
+
# obsoletes QuietMode and FascistLogging
|
33
|
+
#SyslogFacility AUTH
|
34
|
+
SyslogFacility AUTHPRIV
|
35
|
+
#LogLevel INFO
|
36
|
+
|
37
|
+
# Authentication:
|
38
|
+
|
39
|
+
AllowGroups sshusers
|
40
|
+
|
41
|
+
#LoginGraceTime 2m
|
42
|
+
PermitRootLogin without-password
|
43
|
+
#StrictModes yes
|
44
|
+
#MaxAuthTries 6
|
45
|
+
#MaxSessions 10
|
46
|
+
|
47
|
+
#RSAAuthentication yes
|
48
|
+
#PubkeyAuthentication yes
|
49
|
+
#AuthorizedKeysFile .ssh/authorized_keys
|
50
|
+
#AuthorizedKeysCommand none
|
51
|
+
#AuthorizedKeysCommandRunAs nobody
|
52
|
+
|
53
|
+
# For this to work you will also need host keys in /etc/ssh/ssh_known_hosts
|
54
|
+
#RhostsRSAAuthentication no
|
55
|
+
# similar for protocol version 2
|
56
|
+
#HostbasedAuthentication no
|
57
|
+
# Change to yes if you don't trust ~/.ssh/known_hosts for
|
58
|
+
# RhostsRSAAuthentication and HostbasedAuthentication
|
59
|
+
#IgnoreUserKnownHosts no
|
60
|
+
# Don't read the user's ~/.rhosts and ~/.shosts files
|
61
|
+
#IgnoreRhosts yes
|
62
|
+
|
63
|
+
# To disable tunneled clear text passwords, change to no here!
|
64
|
+
#PasswordAuthentication yes
|
65
|
+
#PermitEmptyPasswords no
|
66
|
+
PasswordAuthentication yes
|
67
|
+
|
68
|
+
# Change to no to disable s/key passwords
|
69
|
+
#ChallengeResponseAuthentication yes
|
70
|
+
ChallengeResponseAuthentication no
|
71
|
+
|
72
|
+
# Kerberos options
|
73
|
+
#KerberosAuthentication no
|
74
|
+
#KerberosOrLocalPasswd yes
|
75
|
+
#KerberosTicketCleanup yes
|
76
|
+
#KerberosGetAFSToken no
|
77
|
+
#KerberosUseKuserok yes
|
78
|
+
|
79
|
+
# GSSAPI options
|
80
|
+
#GSSAPIAuthentication no
|
81
|
+
GSSAPIAuthentication yes
|
82
|
+
#GSSAPICleanupCredentials yes
|
83
|
+
GSSAPICleanupCredentials yes
|
84
|
+
#GSSAPIStrictAcceptorCheck yes
|
85
|
+
#GSSAPIKeyExchange no
|
86
|
+
|
87
|
+
# Set this to 'yes' to enable PAM authentication, account processing,
|
88
|
+
# and session processing. If this is enabled, PAM authentication will
|
89
|
+
# be allowed through the ChallengeResponseAuthentication and
|
90
|
+
# PasswordAuthentication. Depending on your PAM configuration,
|
91
|
+
# PAM authentication via ChallengeResponseAuthentication may bypass
|
92
|
+
# the setting of "PermitRootLogin without-password".
|
93
|
+
# If you just want the PAM account and session checks to run without
|
94
|
+
# PAM authentication, then enable this but set PasswordAuthentication
|
95
|
+
# and ChallengeResponseAuthentication to 'no'.
|
96
|
+
#UsePAM no
|
97
|
+
UsePAM yes
|
98
|
+
|
99
|
+
# Accept locale-related environment variables
|
100
|
+
AcceptEnv LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES
|
101
|
+
AcceptEnv LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT
|
102
|
+
AcceptEnv LC_IDENTIFICATION LC_ALL LANGUAGE
|
103
|
+
AcceptEnv XMODIFIERS
|
104
|
+
|
105
|
+
#AllowAgentForwarding yes
|
106
|
+
#AllowTcpForwarding yes
|
107
|
+
#GatewayPorts no
|
108
|
+
#X11Forwarding no
|
109
|
+
X11Forwarding yes
|
110
|
+
#X11DisplayOffset 10
|
111
|
+
#X11UseLocalhost yes
|
112
|
+
#PrintMotd yes
|
113
|
+
#PrintLastLog yes
|
114
|
+
#TCPKeepAlive yes
|
115
|
+
#UseLogin no
|
116
|
+
#UsePrivilegeSeparation yes
|
117
|
+
#PermitUserEnvironment no
|
118
|
+
#Compression delayed
|
119
|
+
#ClientAliveInterval 0
|
120
|
+
#ClientAliveCountMax 3
|
121
|
+
#ShowPatchLevel no
|
122
|
+
#UseDNS yes
|
123
|
+
#PidFile /var/run/sshd.pid
|
124
|
+
#MaxStartups 10
|
125
|
+
#PermitTunnel no
|
126
|
+
#ChrootDirectory none
|
127
|
+
|
128
|
+
# no default banner path
|
129
|
+
#Banner none
|
130
|
+
|
131
|
+
# override default of no subsystems
|
132
|
+
Subsystem sftp /usr/libexec/openssh/sftp-server
|
133
|
+
|
134
|
+
# Example of overriding settings on a per-user basis
|
135
|
+
#Match User anoncvs
|
136
|
+
# X11Forwarding no
|
137
|
+
# AllowTcpForwarding no
|
138
|
+
# ForceCommand cvs server
|
File without changes
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class sshd {
|
2
|
+
augeas { "root login":
|
3
|
+
context => '/files/etc/ssh/sshd_config',
|
4
|
+
changes => 'set PermitRootLogin yes',
|
5
|
+
}
|
6
|
+
|
7
|
+
augeas { "add root login":
|
8
|
+
context => '/files/etc/ssh/sshd_config',
|
9
|
+
changes => [
|
10
|
+
'ins PermitRootLogin after *[last()]',
|
11
|
+
'set PermitRootLogin[last()] yes'
|
12
|
+
],
|
13
|
+
}
|
14
|
+
|
15
|
+
augeas { "fail to add root login":
|
16
|
+
context => '/files/etc/ssh/sshd_config',
|
17
|
+
changes => 'ins PermitRootLogin after *[last()]',
|
18
|
+
}
|
19
|
+
}
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
require 'rspec-puppet-augeas'
|
2
|
+
|
3
|
+
RSpec.configure do |c|
|
4
|
+
c.augeas_fixtures = File.join(File.dirname(File.expand_path(__FILE__)), 'fixtures', 'augeas')
|
5
|
+
c.module_path = File.join(File.dirname(File.expand_path(__FILE__)), 'fixtures', 'modules')
|
6
|
+
c.manifest_dir = File.join(File.dirname(File.expand_path(__FILE__)), 'fixtures', 'manifests')
|
7
|
+
end
|
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rspec-puppet-augeas
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Dominic Cleal
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-01-28 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec-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
|
+
description: RSpec tests for Augeas resources in Puppet manifests
|
31
|
+
email: dcleal@redhat.com
|
32
|
+
executables: []
|
33
|
+
extensions: []
|
34
|
+
extra_rdoc_files: []
|
35
|
+
files:
|
36
|
+
- .gitignore
|
37
|
+
- Gemfile
|
38
|
+
- LICENSE
|
39
|
+
- README.md
|
40
|
+
- Rakefile
|
41
|
+
- lib/rspec-puppet-augeas.rb
|
42
|
+
- lib/rspec-puppet-augeas/example.rb
|
43
|
+
- lib/rspec-puppet-augeas/example/run_augeas_example_group.rb
|
44
|
+
- lib/rspec-puppet-augeas/fixtures.rb
|
45
|
+
- lib/rspec-puppet-augeas/matchers.rb
|
46
|
+
- lib/rspec-puppet-augeas/matchers/execute.rb
|
47
|
+
- lib/rspec-puppet-augeas/resource.rb
|
48
|
+
- lib/rspec-puppet-augeas/test_utils.rb
|
49
|
+
- rspec-puppet-augeas.gemspec
|
50
|
+
- spec/classes/sshd_config_spec.rb
|
51
|
+
- spec/fixtures/augeas/etc/ssh/sshd_config
|
52
|
+
- spec/fixtures/augeas/etc/ssh/sshd_config_2
|
53
|
+
- spec/fixtures/manifests/site.pp
|
54
|
+
- spec/fixtures/modules/sshd/manifests/init.pp
|
55
|
+
- spec/spec_helper.rb
|
56
|
+
homepage: https://github.com/domcleal/rspec-puppet-augeas/
|
57
|
+
licenses: []
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options: []
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ! '>='
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ! '>='
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
requirements: []
|
75
|
+
rubyforge_project:
|
76
|
+
rubygems_version: 1.8.24
|
77
|
+
signing_key:
|
78
|
+
specification_version: 3
|
79
|
+
summary: RSpec tests for Augeas resources in Puppet manifests
|
80
|
+
test_files: []
|