inspec 0.12.0 → 0.14.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.
- 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
|