Class: Inspec::Profile

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Defined in:
lib/inspec/profile.rb

Overview

rubocop:disable Metrics/ClassLength

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(source_reader, options = nil) ⇒ Profile

rubocop:disable Metrics/AbcSize



34
35
36
37
38
39
40
41
# File 'lib/inspec/profile.rb', line 34

def initialize(source_reader, options = nil)
  @options = options || {}
  @target = @options.delete(:target)
  @logger = @options[:logger] || Logger.new(nil)
  @source_reader = source_reader
  @profile_id = @options[:id]
  .finalize(@source_reader., @profile_id)
end

Instance Attribute Details

#pathObject (readonly)

Returns the value of attribute path.



14
15
16
# File 'lib/inspec/profile.rb', line 14

def path
  @path
end

#source_readerObject (readonly)

Returns the value of attribute source_reader.



28
29
30
# File 'lib/inspec/profile.rb', line 28

def source_reader
  @source_reader
end

Class Method Details

.for_target(target, opts) ⇒ Object



16
17
18
19
20
21
22
23
24
25
26
# File 'lib/inspec/profile.rb', line 16

def self.for_target(target, opts)
  # Fetchers retrieve file contents
  opts[:target] = target
  fetcher = Inspec::Fetcher.resolve(target)
  return nil if fetcher.nil?
  # Source readers understand the target's structure and provide
  # access to tests, libraries, and metadata
  reader = Inspec::SourceReader.resolve(fetcher.relative_target)
  return nil if reader.nil?
  new(reader, opts)
end

Instance Method Details

#archive(opts) ⇒ Object

generates a archive of a folder profile assumes that the profile was checked before



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/inspec/profile.rb', line 167

def archive(opts) # rubocop:disable Metrics/AbcSize
  profile_name = params[:name]
  ext = opts[:zip] ? 'zip' : 'tar.gz'

  if opts[:archive]
    archive = Pathname.new(opts[:archive])
  else
    slug = profile_name.downcase.strip.tr(' ', '-').gsub(/[^\w-]/, '_')
    archive = Pathname.new(Dir.pwd).join("#{slug}.#{ext}")
  end

  # check if file exists otherwise overwrite the archive
  if archive.exist? && !opts[:overwrite]
    @logger.info "Archive #{archive} exists already. Use --overwrite."
    return false
  end

  # remove existing archive
  File.delete(archive) if archive.exist?
  @logger.info "Generate archive #{archive}."

  # filter files that should not be part of the profile
  # TODO ignore all .files, but add the files to debug output

  # display all files that will be part of the archive
  @logger.debug 'Add the following files to archive:'
  root_path = @source_reader.target.prefix
  files = @source_reader.target.files
  files.each { |f| @logger.debug '    ' + f }

  if opts[:zip]
    # generate zip archive
    require 'inspec/archive/zip'
    zag = Inspec::Archive::ZipArchiveGenerator.new
    zag.archive(root_path, files, archive)
  else
    # generate tar archive
    require 'inspec/archive/tar'
    tag = Inspec::Archive::TarArchiveGenerator.new
    tag.archive(root_path, files, archive)
  end

  @logger.info 'Finished archive generation.'
  true
end

#checkBoolean

Check if the profile is internall well-structured. The logger will be used to print information on errors and warnings which are found.

Returns:

  • (Boolean)

    true if no errors were found, false otherwise



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/inspec/profile.rb', line 74

def check # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
  # initial values for response object
  result = {
    summary: {
      valid: false,
      timestamp: Time.now.iso8601,
      location: @target,
      profile: nil,
      controls: 0,
    },
    errors: [],
    warnings: [],
  }

  entry = lambda { |file, line, column, control, msg|
    {
      file: file,
      line: line,
      column: column,
      control_id: control,
      msg: msg,
    }
  }

  warn = lambda { |file, line, column, control, msg|
    @logger.warn(msg)
    result[:warnings].push(entry.call(file, line, column, control, msg))
  }

  error = lambda { |file, line, column, control, msg|
    @logger.error(msg)
    result[:errors].push(entry.call(file, line, column, control, msg))
  }

  @logger.info "Checking profile in #{@target}"
  meta_path = @source_reader.target.abs_path(@source_reader..ref)
  if meta_path =~ /metadata\.rb$/
    warn.call(@target, 0, 0, nil, 'The use of `metadata.rb` is deprecated. Use `inspec.yml`.')
  end

  # verify metadata
  m_errors, m_warnings = .valid
  m_errors.each { |msg| error.call(meta_path, 0, 0, nil, msg) }
  m_warnings.each { |msg| warn.call(meta_path, 0, 0, nil, msg) }
  m_unsupported = .unsupported
  m_unsupported.each { |u| warn.call(meta_path, 0, 0, nil, "doesn't support: #{u}") }
  @logger.info 'Metadata OK.' if m_errors.empty? && m_unsupported.empty?

  # extract profile name
  result[:summary][:profile] = .params[:name]

  # check if the profile is using the old test directory instead of the
  # new controls directory
  if @source_reader.tests.keys.any? { |x| x =~ %r{^test/$} }
    warn.call(@target, 0, 0, nil, 'Profile uses deprecated `test` directory, rename it to `controls`.')
  end

  count = rules_count
  result[:summary][:controls] = count
  if count == 0
    warn.call(nil, nil, nil, nil, 'No controls or tests were defined.')
  else
    @logger.info("Found #{count} controls.")
  end

  # iterate over hash of groups
  params[:rules].each { |group, controls|
    @logger.info "Verify all controls in #{group}"
    controls.each { |id, control|
      sfile, sline = control[:source_location]
      error.call(sfile, sline, nil, id, 'Avoid controls with empty IDs') if id.nil? or id.empty?
      next if id.start_with? '(generated '
      warn.call(sfile, sline, nil, id, "Control #{id} has no title") if control[:title].to_s.empty?
      warn.call(sfile, sline, nil, id, "Control #{id} has no description") if control[:desc].to_s.empty?
      warn.call(sfile, sline, nil, id, "Control #{id} has impact > 1.0") if control[:impact].to_f > 1.0
      warn.call(sfile, sline, nil, id, "Control #{id} has impact < 0.0") if control[:impact].to_f < 0.0
      warn.call(sfile, sline, nil, id, "Control #{id} has no tests defined") if control[:checks].nil? or control[:checks].empty?
    }
  }

  # profile is valid if we could not find any error
  result[:summary][:valid] = result[:errors].empty?

  @logger.info 'Control definitions OK.' if result[:warnings].empty?
  result
end

#infoObject



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/inspec/profile.rb', line 47

def info
  res = params.dup
  rules = {}
  res[:rules].each do |gid, group|
    next if gid.to_s.empty?
    rules[gid] = { title: gid, rules: {} }
    group.each do |id, rule|
      next if id.to_s.empty?
      data = rule.dup
      data.delete(:checks)
      data[:impact] ||= 0.5
      data[:impact] = 1.0 if data[:impact] > 1.0
      data[:impact] = 0.0 if data[:impact] < 0.0
      rules[gid][:rules][id] = data
      # TODO: temporarily flatten the group down; replace this with
      # proper hierarchy later on
      rules[gid][:title] = data[:group_title]
    end
  end
  res[:rules] = rules
  res
end

#paramsObject



43
44
45
# File 'lib/inspec/profile.rb', line 43

def params
  @params ||= load_params
end

#rules_countObject



161
162
163
# File 'lib/inspec/profile.rb', line 161

def rules_count
  params[:rules].values.map { |hm| hm.values.length }.inject(:+) || 0
end