rspec-puppet-augeas 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 +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: []
|