inspec 0.12.0 → 0.14.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +39 -2
- data/bin/inspec +11 -9
- data/docs/matchers.rst +129 -0
- data/docs/resources.rst +64 -37
- data/inspec.gemspec +1 -1
- data/lib/bundles/inspec-compliance/cli.rb +1 -1
- data/lib/bundles/inspec-compliance/configuration.rb +1 -0
- data/lib/bundles/inspec-compliance/target.rb +16 -32
- data/lib/bundles/inspec-init/cli.rb +2 -0
- data/lib/bundles/inspec-supermarket.rb +13 -0
- data/lib/bundles/inspec-supermarket/api.rb +2 -0
- data/lib/bundles/inspec-supermarket/cli.rb +2 -2
- data/lib/bundles/inspec-supermarket/target.rb +11 -15
- data/lib/fetchers/local.rb +31 -0
- data/lib/fetchers/tar.rb +48 -0
- data/lib/fetchers/url.rb +100 -0
- data/lib/fetchers/zip.rb +47 -0
- data/lib/inspec.rb +2 -3
- data/lib/inspec/fetcher.rb +22 -0
- data/lib/inspec/metadata.rb +4 -2
- data/lib/inspec/plugins.rb +2 -0
- data/lib/inspec/plugins/fetcher.rb +97 -0
- data/lib/inspec/plugins/source_reader.rb +36 -0
- data/lib/inspec/profile.rb +92 -81
- data/lib/inspec/resource.rb +1 -0
- data/lib/inspec/runner.rb +15 -35
- data/lib/inspec/source_reader.rb +32 -0
- data/lib/inspec/version.rb +1 -1
- data/lib/matchers/matchers.rb +5 -6
- data/lib/resources/file.rb +8 -2
- data/lib/resources/passwd.rb +71 -45
- data/lib/resources/service.rb +13 -9
- data/lib/resources/shadow.rb +135 -0
- data/lib/source_readers/flat.rb +38 -0
- data/lib/source_readers/inspec.rb +78 -0
- data/lib/utils/base_cli.rb +2 -2
- data/lib/utils/parser.rb +1 -1
- data/lib/utils/plugin_registry.rb +93 -0
- data/test/docker_test.rb +1 -1
- data/test/helper.rb +62 -2
- data/test/integration/cookbooks/os_prepare/recipes/service.rb +4 -2
- data/test/integration/test/integration/default/compare_matcher_spec.rb +11 -0
- data/test/integration/test/integration/default/service_spec.rb +16 -1
- data/test/unit/fetchers.rb +61 -0
- data/test/unit/fetchers/local_test.rb +67 -0
- data/test/unit/fetchers/tar_test.rb +36 -0
- data/test/unit/fetchers/url_test.rb +152 -0
- data/test/unit/fetchers/zip_test.rb +36 -0
- data/test/unit/mock/files/passwd +1 -1
- data/test/unit/mock/files/shadow +2 -0
- data/test/unit/mock/profiles/complete-profile/libraries/testlib.rb +1 -0
- data/test/unit/plugin_test.rb +0 -1
- data/test/unit/profile_test.rb +32 -53
- data/test/unit/resources/passwd_test.rb +69 -14
- data/test/unit/resources/shadow_test.rb +67 -0
- data/test/unit/source_reader_test.rb +17 -0
- data/test/unit/source_readers/flat_test.rb +61 -0
- data/test/unit/source_readers/inspec_test.rb +38 -0
- data/test/unit/utils/passwd_parser_test.rb +1 -1
- metadata +40 -21
- data/lib/inspec/targets.rb +0 -10
- data/lib/inspec/targets/archive.rb +0 -33
- data/lib/inspec/targets/core.rb +0 -56
- data/lib/inspec/targets/dir.rb +0 -144
- data/lib/inspec/targets/file.rb +0 -33
- data/lib/inspec/targets/folder.rb +0 -38
- data/lib/inspec/targets/tar.rb +0 -61
- data/lib/inspec/targets/url.rb +0 -78
- data/lib/inspec/targets/zip.rb +0 -55
- data/test/unit/targets.rb +0 -132
data/lib/inspec/targets.rb
DELETED
@@ -1,10 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
# author: Dominik Richter
|
3
|
-
# author: Christoph Hartmann
|
4
|
-
|
5
|
-
require 'inspec/targets/core'
|
6
|
-
require 'inspec/targets/file'
|
7
|
-
require 'inspec/targets/folder'
|
8
|
-
require 'inspec/targets/url'
|
9
|
-
require 'inspec/targets/zip'
|
10
|
-
require 'inspec/targets/tar'
|
@@ -1,33 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
# author: Dominik Richter
|
3
|
-
# author: Christoph Hartmann
|
4
|
-
|
5
|
-
require 'rubygems/package'
|
6
|
-
require 'zlib'
|
7
|
-
require 'inspec/targets/dir'
|
8
|
-
|
9
|
-
module Inspec::Targets
|
10
|
-
class ArchiveHelper < DirsResolver
|
11
|
-
attr_reader :files
|
12
|
-
|
13
|
-
def initialize(target, _opts = {})
|
14
|
-
@target = target
|
15
|
-
files, @rootdir = structure(target)
|
16
|
-
|
17
|
-
# remove trailing slashes
|
18
|
-
files = files.collect { |f| f.chomp('/') }
|
19
|
-
|
20
|
-
# remove leading directory
|
21
|
-
root = Pathname(@rootdir)
|
22
|
-
@files = files.collect { |f|
|
23
|
-
Pathname(f).relative_path_from(root).to_s
|
24
|
-
}
|
25
|
-
end
|
26
|
-
|
27
|
-
def resolve(path, opts = {})
|
28
|
-
o = (opts || {})
|
29
|
-
o[:base_folder] = @target
|
30
|
-
content(@target, path, @rootdir, o)
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
data/lib/inspec/targets/core.rb
DELETED
@@ -1,56 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
# author: Dominik Richter
|
3
|
-
# author: Christoph Hartmann
|
4
|
-
|
5
|
-
require 'utils/modulator'
|
6
|
-
|
7
|
-
module Inspec
|
8
|
-
module Targets
|
9
|
-
extend Modulator
|
10
|
-
|
11
|
-
# Retrieve the resolver for a given target. Resolvers are any type of
|
12
|
-
# instance, that can take a target in the #resolve method, and turn
|
13
|
-
# it into raw InSpec code. It will actually retrieve all file's
|
14
|
-
# contents, including libraries and metadata.
|
15
|
-
#
|
16
|
-
# @param [any] target any target you are pointing to; typically a string
|
17
|
-
# @return [Inspec::Targets::*] the resolver handling this target
|
18
|
-
def self.find_resolver(target)
|
19
|
-
modules.values.find { |m| m.handles?(target) }
|
20
|
-
end
|
21
|
-
|
22
|
-
# Retrieve the target handler, i.e. the component that is going to handle
|
23
|
-
# all aspects of a given target. Unlike the resolver, this provides
|
24
|
-
# access to an object, that understands the target and is able to read
|
25
|
-
# all contents, while providing additional methods which InSpec itself
|
26
|
-
# does not care about (think: plugins).
|
27
|
-
#
|
28
|
-
# There is a special case, where you might be pointing to a URL containing
|
29
|
-
# a ZIP file which has a full InSpec Profile embedded. This will be
|
30
|
-
# resolved to a directory helper (Inspec::Targets::DirsResolver). This
|
31
|
-
# method will retrieve the right handler for this directory and provide it
|
32
|
-
# with full access all contents (i.e. the DirsResolver will be embedded).
|
33
|
-
#
|
34
|
-
# @param [any] target any target you are pointing to; typically a string
|
35
|
-
# @return [Inspec::Targets::*] the handler for this target
|
36
|
-
def self.find_handler(target)
|
37
|
-
resolver = find_resolver(target)
|
38
|
-
return resolver unless resolver.is_a?(Module) &&
|
39
|
-
resolver.ancestors.include?(DirsResolver)
|
40
|
-
resolver.from_target(target).handler
|
41
|
-
end
|
42
|
-
|
43
|
-
# Turn targets into actionable InSpec code, library-data and metadata.
|
44
|
-
#
|
45
|
-
# @param [any] targets any targets you are pointing to; typically strings
|
46
|
-
# @param [Hash] additional options for loading
|
47
|
-
# @return [Array] an array of resolved data, with :content, :ref, :type
|
48
|
-
def self.resolve(targets, opts = {})
|
49
|
-
Array(targets).map do |target|
|
50
|
-
handler = find_resolver(target) ||
|
51
|
-
fail("Don't know how to handle target: #{target}")
|
52
|
-
handler.resolve(target, opts)
|
53
|
-
end.flatten
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
data/lib/inspec/targets/dir.rb
DELETED
@@ -1,144 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
# author: Dominik Richter
|
3
|
-
# author: Christoph Hartmann
|
4
|
-
|
5
|
-
module Inspec::Targets
|
6
|
-
# DirsHelper manages resolvers for directories. Wherever directories are,
|
7
|
-
# either a local folder, archive, or remote, this module provides all helpers
|
8
|
-
# than can resolve these.
|
9
|
-
#
|
10
|
-
# Resolvers provide these methods
|
11
|
-
# * handles?(paths) => true if the resolver is responsible for this target
|
12
|
-
# * get_filenames => Array[String] of files where tests/controls are found
|
13
|
-
# * get_metadata => String path with metadata information [optional]
|
14
|
-
# * get_libraries => Array[String] of library files [optional]
|
15
|
-
#
|
16
|
-
# Resolvers must register with this DirsHelper via #add_module .
|
17
|
-
module DirsHelper
|
18
|
-
extend Modulator
|
19
|
-
|
20
|
-
def self.get_handler(paths)
|
21
|
-
modules.values.reverse.find { |x| x.handles?(paths) }
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
# Base class for all directory resolvers. I.e. any target helper that
|
26
|
-
# takes a directory/archive/... and ultimately calls DirsHelper to resolve it.
|
27
|
-
#
|
28
|
-
# These resolvers must implement the required methods of this class.
|
29
|
-
class DirsResolver
|
30
|
-
def files
|
31
|
-
fail NotImplementedError, "Directory resolver #{self.class} must implement #files"
|
32
|
-
end
|
33
|
-
|
34
|
-
def resolve(_path)
|
35
|
-
fail NotImplementedError, "Directory resolver #{self.class} must implement #content"
|
36
|
-
end
|
37
|
-
|
38
|
-
def handler
|
39
|
-
DirsHelper.get_handler(files) ||
|
40
|
-
fail("Don't know how to handle #{self}")
|
41
|
-
end
|
42
|
-
|
43
|
-
def self.from_target(target)
|
44
|
-
new(target)
|
45
|
-
end
|
46
|
-
|
47
|
-
def self.resolve(target, opts)
|
48
|
-
r = new(target, opts)
|
49
|
-
handler = r.handler
|
50
|
-
files = r.files
|
51
|
-
|
52
|
-
res = []
|
53
|
-
{
|
54
|
-
test: __collect(handler, :get_filenames, files),
|
55
|
-
library: __collect(handler, :get_libraries, files),
|
56
|
-
metadata: __collect(handler, :get_metadata, files),
|
57
|
-
}.each do |as, list|
|
58
|
-
Array(list).each { |path|
|
59
|
-
res.push(r.resolve(path, as: as))
|
60
|
-
}
|
61
|
-
end
|
62
|
-
|
63
|
-
return handler.resolve_contents(res) if handler.respond_to?(:resolve_contents)
|
64
|
-
res
|
65
|
-
end
|
66
|
-
|
67
|
-
def self.__collect(handler, getter, files)
|
68
|
-
return [] unless handler.respond_to? getter
|
69
|
-
handler.method(getter).call(files)
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
# InSpec profile Loader
|
74
|
-
# Previous versions used the `test` directory instead of the new `controls`
|
75
|
-
# directory. Usage of the test directory is deprecated and not recommended
|
76
|
-
# anymore. Support for `test` will be removed in InSpec 1.0
|
77
|
-
# TODO: remove `test` support for InSpec 1.0
|
78
|
-
class ProfileDir
|
79
|
-
def handles?(paths)
|
80
|
-
return true if paths.include?('inspec.yml')
|
81
|
-
(
|
82
|
-
!paths.grep(/^controls/).empty? ||
|
83
|
-
!paths.grep(/^test/).empty?
|
84
|
-
) && paths.include?('metadata.rb')
|
85
|
-
end
|
86
|
-
|
87
|
-
def get_libraries(paths)
|
88
|
-
paths.find_all do |path|
|
89
|
-
path.start_with?('libraries') && path.end_with?('.rb')
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
def get_filenames(paths)
|
94
|
-
paths.find_all do |path|
|
95
|
-
path.start_with?('controls', 'test') && path.end_with?('.rb')
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
def get_metadata(paths)
|
100
|
-
return 'inspec.yml' if paths.include?('inspec.yml')
|
101
|
-
return 'metadata.rb' if paths.include?('metadata.rb')
|
102
|
-
end
|
103
|
-
end
|
104
|
-
DirsHelper.add_module('inspec-profile', ProfileDir.new)
|
105
|
-
|
106
|
-
class ChefAuditDir
|
107
|
-
def handles?(paths)
|
108
|
-
paths.include?('recipes') and paths.include?('metadata.rb')
|
109
|
-
end
|
110
|
-
|
111
|
-
def get_filenames(paths)
|
112
|
-
paths.find_all do |x|
|
113
|
-
x.start_with? 'recipes/' and x.end_with? '.rb'
|
114
|
-
end
|
115
|
-
end
|
116
|
-
end
|
117
|
-
DirsHelper.add_module('chef-cookbook', ChefAuditDir.new)
|
118
|
-
|
119
|
-
class ServerspecDir
|
120
|
-
def handles?(paths)
|
121
|
-
paths.include?('spec')
|
122
|
-
end
|
123
|
-
|
124
|
-
def get_filenames(paths)
|
125
|
-
paths.find_all do |path|
|
126
|
-
path.start_with? 'spec' and path.end_with? '_spec.rb'
|
127
|
-
end
|
128
|
-
end
|
129
|
-
end
|
130
|
-
DirsHelper.add_module('serverspec-folder', ServerspecDir.new)
|
131
|
-
|
132
|
-
class FlatDir
|
133
|
-
def handles?(paths)
|
134
|
-
get_filenames(paths).empty? == false
|
135
|
-
end
|
136
|
-
|
137
|
-
def get_filenames(paths)
|
138
|
-
# TODO: eventually remove the metadata.rb exception here
|
139
|
-
# when we have fully phased out metadata.rb in 1.0
|
140
|
-
paths.find_all { |x| x.end_with?('.rb') && !x.include?('/') && x != 'metadata.rb' }
|
141
|
-
end
|
142
|
-
end
|
143
|
-
DirsHelper.add_module('flat-ruby-folder', FlatDir.new)
|
144
|
-
end
|
data/lib/inspec/targets/file.rb
DELETED
@@ -1,33 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
# author: Dominik Richter
|
3
|
-
# author: Christoph Hartmann
|
4
|
-
|
5
|
-
module Inspec::Targets
|
6
|
-
class FileHelper
|
7
|
-
def handles?(target)
|
8
|
-
File.file?(target) and target.end_with?('.rb')
|
9
|
-
end
|
10
|
-
|
11
|
-
def resolve(target, opts = {})
|
12
|
-
base = opts[:base_folder]
|
13
|
-
path = base.nil? ? target : File.join(base, target)
|
14
|
-
{
|
15
|
-
content: File.read(path),
|
16
|
-
type: opts[:as] || :test,
|
17
|
-
ref: path,
|
18
|
-
}
|
19
|
-
end
|
20
|
-
|
21
|
-
def resolve_all(targets, opts = {})
|
22
|
-
Array(targets).map do |target|
|
23
|
-
resolve(target, opts)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
def to_s
|
28
|
-
'File Loader'
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
Inspec::Targets.add_module('file', FileHelper.new)
|
33
|
-
end
|
@@ -1,38 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
# author: Dominik Richter
|
3
|
-
# author: Christoph Hartmann
|
4
|
-
|
5
|
-
require 'inspec/targets/dir'
|
6
|
-
require 'inspec/targets/file'
|
7
|
-
|
8
|
-
module Inspec::Targets
|
9
|
-
class FolderHelper < DirsResolver
|
10
|
-
def self.handles?(target)
|
11
|
-
File.directory?(target)
|
12
|
-
end
|
13
|
-
|
14
|
-
attr_reader :files
|
15
|
-
|
16
|
-
def initialize(target, _opts = {})
|
17
|
-
@base_folder = target
|
18
|
-
# find all files in the folder
|
19
|
-
files = Dir[File.join(target, '**', '*')]
|
20
|
-
# remove the prefix
|
21
|
-
prefix = File.join(target, '')
|
22
|
-
@files = files.map { |x| x.sub(prefix, '') }
|
23
|
-
@file_handler = Inspec::Targets.modules['file']
|
24
|
-
end
|
25
|
-
|
26
|
-
def resolve(path, opts = {})
|
27
|
-
o = (opts || {})
|
28
|
-
o[:base_folder] = @base_folder
|
29
|
-
@file_handler.resolve(path, o)
|
30
|
-
end
|
31
|
-
|
32
|
-
def to_s
|
33
|
-
'Folder Loader'
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
Inspec::Targets.add_module('folder', FolderHelper)
|
38
|
-
end
|
data/lib/inspec/targets/tar.rb
DELETED
@@ -1,61 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
# author: Dominik Richter
|
3
|
-
# author: Christoph Hartmann
|
4
|
-
|
5
|
-
require 'rubygems/package'
|
6
|
-
require 'zlib'
|
7
|
-
require 'inspec/targets/archive'
|
8
|
-
|
9
|
-
module Inspec::Targets
|
10
|
-
class TarHelper < ArchiveHelper
|
11
|
-
def self.handles?(target)
|
12
|
-
File.file?(target) && target.end_with?('.tar.gz', '.tgz')
|
13
|
-
end
|
14
|
-
|
15
|
-
def structure(input)
|
16
|
-
files = []
|
17
|
-
rootdir = ''
|
18
|
-
Gem::Package::TarReader.new(Zlib::GzipReader.open(input)) do |tar|
|
19
|
-
files = tar.map(&:full_name)
|
20
|
-
end
|
21
|
-
|
22
|
-
# find root dir of profile
|
23
|
-
files.each { |f|
|
24
|
-
pn = Pathname(f)
|
25
|
-
rootdir = pn.dirname.to_s if pn.basename.to_s == 'inspec.yml' || pn.basename.to_s == 'metadata.rb'
|
26
|
-
}
|
27
|
-
|
28
|
-
# stores the rootdir of metadata.rb or inspec.yml
|
29
|
-
rootdir += '/' if !rootdir.empty?
|
30
|
-
[files, rootdir]
|
31
|
-
end
|
32
|
-
|
33
|
-
def content(input, path, rootdir = nil, opts = {})
|
34
|
-
content = nil
|
35
|
-
Gem::Package::TarReader.new(Zlib::GzipReader.open(input)) do |tar|
|
36
|
-
tar.each do |entry|
|
37
|
-
if entry.directory?
|
38
|
-
# nothing to do
|
39
|
-
elsif entry.file?
|
40
|
-
next unless path == entry.full_name.gsub(rootdir, '')
|
41
|
-
content = {
|
42
|
-
# NB if some file is empty, return empty-string, not nil
|
43
|
-
content: entry.read || '',
|
44
|
-
type: opts[:as] || :test,
|
45
|
-
ref: entry.full_name,
|
46
|
-
}
|
47
|
-
elsif entry.header.typeflag == '2'
|
48
|
-
# ignore symlinks for now
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
content
|
53
|
-
end
|
54
|
-
|
55
|
-
def to_s
|
56
|
-
'tar.gz Loader'
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
Inspec::Targets.add_module('tar', TarHelper)
|
61
|
-
end
|
data/lib/inspec/targets/url.rb
DELETED
@@ -1,78 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
# author: Dominik Richter
|
3
|
-
# author: Christoph Hartmann
|
4
|
-
|
5
|
-
require 'uri'
|
6
|
-
require 'tempfile'
|
7
|
-
require 'open-uri'
|
8
|
-
require 'inspec/targets/zip'
|
9
|
-
|
10
|
-
module Inspec::Targets
|
11
|
-
class UrlHelper
|
12
|
-
def handles?(target)
|
13
|
-
uri = URI.parse(target)
|
14
|
-
return false if uri.nil? or uri.scheme.nil?
|
15
|
-
%{ http https }.include? uri.scheme
|
16
|
-
rescue URI::Error => _e
|
17
|
-
false
|
18
|
-
end
|
19
|
-
|
20
|
-
def resolve(target, opts = {})
|
21
|
-
# abort if the target does not start with http or https
|
22
|
-
return nil unless target.start_with?('https://', 'http://')
|
23
|
-
|
24
|
-
# support for github url
|
25
|
-
m = %r{^https?://(www\.)?github\.com/(?<user>[\w-]+)/(?<repo>[\w-]+)(\.git)?(/)?$}.match(target)
|
26
|
-
if m
|
27
|
-
url = "https://github.com/#{m[:user]}/#{m[:repo]}/archive/master.tar.gz"
|
28
|
-
else
|
29
|
-
url = target
|
30
|
-
end
|
31
|
-
resolve_archive(url, opts)
|
32
|
-
end
|
33
|
-
|
34
|
-
# download url into archive using opts,
|
35
|
-
# returns File object and content-type from HTTP headers
|
36
|
-
def download_archive(url, archive, opts)
|
37
|
-
archive.binmode
|
38
|
-
remote = open(
|
39
|
-
url,
|
40
|
-
http_basic_authentication: [opts['user'] || '', opts['password'] || ''],
|
41
|
-
)
|
42
|
-
|
43
|
-
# download content
|
44
|
-
archive.write(remote.read)
|
45
|
-
archive.rewind
|
46
|
-
archive.close
|
47
|
-
|
48
|
-
[archive, remote.meta['content-type']]
|
49
|
-
end
|
50
|
-
|
51
|
-
def resolve_archive(url, opts)
|
52
|
-
archive, content_type = download_archive(url, Tempfile.new(['inspec-dl-', '.tar.gz']), opts)
|
53
|
-
|
54
|
-
# replace extension with zip if we detected a zip content type
|
55
|
-
if ['application/x-zip-compressed', 'application/zip'].include?(content_type)
|
56
|
-
# rename file for proper detection in DirHelper
|
57
|
-
pn = Pathname.new(archive.path)
|
58
|
-
new_path = pn.dirname.join(pn.basename.to_s.gsub('tar.gz', 'zip'))
|
59
|
-
File.rename(pn.to_s, new_path.to_s)
|
60
|
-
|
61
|
-
content = ZipHelper.new.resolve(new_path)
|
62
|
-
File.unlink(new_path)
|
63
|
-
elsif ['application/x-gzip', 'application/gzip'].include?(content_type)
|
64
|
-
# use tar helper as default (otherwise returns nil)
|
65
|
-
content = TarHelper.new.resolve(archive.path)
|
66
|
-
archive.unlink
|
67
|
-
end
|
68
|
-
|
69
|
-
content
|
70
|
-
end
|
71
|
-
|
72
|
-
def to_s
|
73
|
-
'URL Loader'
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
Inspec::Targets.add_module('url', UrlHelper.new)
|
78
|
-
end
|