mirror of
https://github.com/inspec/inspec
synced 2025-02-16 22:18:38 +00:00
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:
parent
d0f2e49970
commit
3e7d47505c
10 changed files with 175 additions and 2 deletions
75
docs/resources/xml.md.erb
Normal file
75
docs/resources/xml.md.erb
Normal 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
|
|
@ -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
27
lib/resources/xml.rb
Normal 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
|
14
test/cookbooks/os_prepare/files/example.xml
Normal file
14
test/cookbooks/os_prepare/files/example.xml
Normal 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>
|
|
@ -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')
|
||||
|
|
|
@ -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
|
|
@ -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'),
|
||||
|
|
12
test/integration/default/controls/xml_spec.rb
Normal file
12
test/integration/default/controls/xml_spec.rb
Normal 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
|
14
test/unit/mock/files/default.xml
Normal file
14
test/unit/mock/files/default.xml
Normal 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>
|
29
test/unit/resources/xml_test.rb
Normal file
29
test/unit/resources/xml_test.rb
Normal 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
|
Loading…
Add table
Reference in a new issue