Add support for XML files (#2107)

* Add support for XML files

Signed-off-by: Morley, Jonathan <jmorley@cvent.com>

* Use REXML instead of nokogiri

Signed-off-by: Morley, Jonathan <jmorley@cvent.com>
This commit is contained in:
Jonathan Morley 2017-08-31 03:56:14 -04:00 committed by Dominik Richter
parent d0f2e49970
commit 3e7d47505c
10 changed files with 175 additions and 2 deletions

75
docs/resources/xml.md.erb Normal file
View file

@ -0,0 +1,75 @@
---
title: About the xml Resource
---
# xml
Use the `xml` InSpec audit resource to test data in an XML file.
## Syntax
An `xml` resource block declares the data to be tested. Assume the following XML file:
<root>
<name>hello</name>
<meta>
<creator>John Doe</creator>
</meta>
<array>
<element>one</element>
<element>two</element>
</array>
</root>
This file can be queried using:
describe xml('/path/to/name.xml') do
its('root/name') { should eq ['hello'] }
its('root/meta/creator') { should eq ['John Doe'] }
its('root/array[2]/element]) { should eq ['two'] }
end
where
* `root/name` is an XPath expression
* `should eq ['foo']` tests a value of `root/name` as read from an XML file versus the value declared in the test
## Matchers
This InSpec audit resource has the following matchers:
### be
<%= partial "/shared/matcher_be" %>
### cmp
<%= partial "/shared/matcher_cmp" %>
### eq
<%= partial "/shared/matcher_eq" %>
### include
<%= partial "/shared/matcher_include" %>
### match
<%= partial "/shared/matcher_match" %>
### name
The `name` matcher tests the value of `name` as read from a JSON file versus the value declared in the test:
its('name') { should eq 'foo' }
## Examples
The following examples show how to use this InSpec audit resource.
### Test an AppPool's presence in an applicationHost.config file
describe xml('applicationHost.config') do
its('configuration/system.applicationHost/applicationPools/add@name') { should contain('my_pool') }
end

View file

@ -158,3 +158,4 @@ require 'resources/json'
require 'resources/yaml'
require 'resources/csv'
require 'resources/ini'
require 'resources/xml'

27
lib/resources/xml.rb Normal file
View file

@ -0,0 +1,27 @@
# encoding: utf-8
# author: Jonathan Morley
module Inspec::Resources
class XmlConfig < JsonConfig
name 'xml'
desc 'Use the xml InSpec resource to test configuration data in an XML file'
example "
describe xml('default.xml') do
its('key/sub_key') { should eq(['value']) }
end
"
def parse(content)
require 'rexml/document'
REXML::Document.new(content)
end
def value(key)
REXML::XPath.each(@params, key.first.to_s).map(&:text)
end
def to_s
"XML #{@path}"
end
end
end

View file

@ -0,0 +1,14 @@
<breakfast_menu>
<food>
<name>Belgian Waffles</name>
<price>$5.95</price>
<description>Two of our famous Belgian Waffles with plenty of real maple syrup</description>
<calories>650</calories>
</food>
<food>
<name>Strawberry Belgian Waffles</name>
<price>$7.95</price>
<description>Light Belgian waffles covered with strawberries and whipped cream</description>
<calories>900</calories>
</food>
</breakfast_menu>

View file

@ -22,7 +22,7 @@ include_recipe('os_prepare::x509')
include_recipe('os_prepare::dh_params')
# config file parsing
include_recipe('os_prepare::json_yaml_csv_ini')
include_recipe('os_prepare::json_yaml_csv_ini_xml')
# configure repos, eg. nginx
include_recipe('os_prepare::apt')

View file

@ -15,7 +15,7 @@ gid = case node['platform_family']
'root'
end
['yml', 'json', 'csv', 'ini', 'toml'].each { |filetype|
['yml', 'json', 'csv', 'ini', 'toml', 'xml'].each { |filetype|
if node['platform_family'] != 'windows'
cookbook_file "/tmp/example.#{filetype}" do

View file

@ -153,6 +153,7 @@ class MockLoader
# Test DH parameters, 2048 bit long safe prime, generator 2 for dh_params in PEM format
'dh_params.dh_pem' => mockfile.call('dh_params.dh_pem'),
'default.toml' => mockfile.call('default.toml'),
'default.xml' => mockfile.call('default.xml'),
'/test/path/to/postgres/pg_hba.conf' => mockfile.call('pg_hba.conf'),
'/etc/postgresql/9.5/main/pg_ident.conf' => mockfile.call('pg_ident.conf'),
'C:/etc/postgresql/9.5/main/pg_ident.conf' => mockfile.call('pg_ident.conf'),

View file

@ -0,0 +1,12 @@
# encoding: utf-8
if os.unix?
filename = '/tmp/example.xml'
else
filename = 'c:/windows/temp/example.xml'
end
describe xml(filename) do
its ('/breakfast_menu/food[1]/name') { should eq(['Belgian Waffles']) }
its ('/breakfast_menu/food/name') { should eq(['Belgian Waffles', 'Strawberry Belgian Waffles']) }
end

View file

@ -0,0 +1,14 @@
<breakfast_menu>
<food>
<name>Belgian Waffles</name>
<price>$5.95</price>
<description>Two of our famous Belgian Waffles with plenty of real maple syrup</description>
<calories>650</calories>
</food>
<food>
<name>Strawberry Belgian Waffles</name>
<price>$7.95</price>
<description>Light Belgian waffles covered with strawberries and whipped cream</description>
<calories>900</calories>
</food>
</breakfast_menu>

View file

@ -0,0 +1,29 @@
# encoding: utf-8
require 'helper'
require 'inspec/resource'
require 'rexml/document'
describe 'Inspec::Resources::XML' do
describe 'when loading valid XML' do
let (:resource) { load_resource('xml', 'default.xml') }
it 'gets params as a document' do
_(resource.params).must_be_kind_of REXML::Document
end
it 'retrieves empty array if xpath cannot be found' do
_(resource.send('missing')).must_equal []
end
it 'retrieves xpath by name' do
_(resource.send('breakfast_menu/food[1]/name')).must_equal ['Belgian Waffles']
_(resource.send('/breakfast_menu/food[1]/name')).must_equal ['Belgian Waffles']
end
it 'retrieves many xpaths by name' do
_(resource.send('/breakfast_menu/food/name')).must_equal ['Belgian Waffles', 'Strawberry Belgian Waffles']
_(resource.send('//name')).must_equal ['Belgian Waffles', 'Strawberry Belgian Waffles']
end
end
end