inspec 1.6.0 → 1.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cd0cb42f326fa8d1e5c78e7cb2d190912f29dbe8
4
- data.tar.gz: 1a6b9b13d1c21616d2ab9af800fac73fd573ff8f
3
+ metadata.gz: a69a3a78ee7d17ad89e5d13c6445346393e1e1a1
4
+ data.tar.gz: 03059ff1d249a73bc2f49bdfbac8f162eb3ee3ab
5
5
  SHA512:
6
- metadata.gz: 189769e535f062fb5bc6d8f1ea1f16f3fa24a0904f816216ea5cbf810ec38cc87eeb19171d80ce41fed1d52ebeb1a57e07d5aef3098132ee1ad39e0e0726a131
7
- data.tar.gz: 9b456f2b72df9b323c1cb9a96a917bfafe2229aa5fabf8507694ba61d75b48477589a6047750c5cbf592f28e0d691a5c589149c44d554761b7008663abc58b49
6
+ metadata.gz: c71b46c3f283a84725615ebcdab4d8b0e58e301a7bd53ba0903a4abbd7e0d54af271b2d24dc21d1cafcbade09ced2ee5b1a4cc2af55594d47d925b9b39e13e46
7
+ data.tar.gz: db25eac456c658775d97fa388e68b7d1e8a39562615cfdaf5d76647d9045c5b4a4ab914f2a12d3321e4e44261f02eaa0ef8ea0017dc667663b98677323cb8cac
@@ -1,7 +1,39 @@
1
1
  # Change Log
2
2
 
3
- ## [1.6.0](https://github.com/chef/inspec/tree/1.6.0) (2016-11-28)
4
- [Full Changelog](https://github.com/chef/inspec/compare/v1.5.0...1.6.0)
3
+ ## [1.7.0](https://github.com/chef/inspec/tree/1.7.0) (2016-12-02)
4
+ [Full Changelog](https://github.com/chef/inspec/compare/v1.6.0...1.7.0)
5
+
6
+ **Implemented enhancements:**
7
+
8
+ - inspec compliance profiles should support automate as a backend [\#1295](https://github.com/chef/inspec/issues/1295)
9
+ - this is killing my eyes!!!!!!!!! [\#951](https://github.com/chef/inspec/issues/951)
10
+ - Show process name during inspec output [\#1329](https://github.com/chef/inspec/pull/1329) ([jcastillocano](https://github.com/jcastillocano))
11
+
12
+ **Fixed bugs:**
13
+
14
+ - inspec.lock not loaded from tarball profiles [\#1322](https://github.com/chef/inspec/issues/1322)
15
+ - InSpec tries to re-fetch profiles even if lockfile exists [\#1316](https://github.com/chef/inspec/issues/1316)
16
+ - fix docker release script [\#1328](https://github.com/chef/inspec/pull/1328) ([chris-rock](https://github.com/chris-rock))
17
+ - Provide inspec.lock for archives as well [\#1323](https://github.com/chef/inspec/pull/1323) ([alexpop](https://github.com/alexpop))
18
+ - inspec check and json to use vendored dependencies [\#1321](https://github.com/chef/inspec/pull/1321) ([alexpop](https://github.com/alexpop))
19
+
20
+ **Closed issues:**
21
+
22
+ - RegExp in processes resource can't match long-run process [\#1332](https://github.com/chef/inspec/issues/1332)
23
+ - inspec archive vendoring [\#1325](https://github.com/chef/inspec/issues/1325)
24
+ - inspec compliance upload of a meta-profile [\#1294](https://github.com/chef/inspec/issues/1294)
25
+
26
+ **Merged pull requests:**
27
+
28
+ - improve functional tests for vendored profiles [\#1337](https://github.com/chef/inspec/pull/1337) ([chris-rock](https://github.com/chris-rock))
29
+ - Adds junit to the inspec help exec [\#1336](https://github.com/chef/inspec/pull/1336) ([burtlo](https://github.com/burtlo))
30
+ - Vendor profile when uploading to chef-compliance [\#1334](https://github.com/chef/inspec/pull/1334) ([vjeffrey](https://github.com/vjeffrey))
31
+ - fix bug: RegExp in processes resource can't match long-run process \#1332 [\#1333](https://github.com/chef/inspec/pull/1333) ([Wing924](https://github.com/Wing924))
32
+ - clean up rspec\_json\_formatter [\#1314](https://github.com/chef/inspec/pull/1314) ([vjeffrey](https://github.com/vjeffrey))
33
+ - enable inspec compliance cli support automate [\#1297](https://github.com/chef/inspec/pull/1297) ([vjeffrey](https://github.com/vjeffrey))
34
+
35
+ ## [v1.6.0](https://github.com/chef/inspec/tree/v1.6.0) (2016-11-28)
36
+ [Full Changelog](https://github.com/chef/inspec/compare/v1.5.0...v1.6.0)
5
37
 
6
38
  **Fixed bugs:**
7
39
 
data/Rakefile CHANGED
@@ -163,8 +163,8 @@ task :release_docker do
163
163
  cmd = "rm *.gem; gem build *gemspec && "\
164
164
  "mv *.gem inspec.gem && "\
165
165
  "docker build -t chef/inspec:#{version} . && "\
166
- "docker push chef/inspec:#{version}"
167
- "docker tag chef/inspec:#{version} chef/inspec:latest"
166
+ "docker push chef/inspec:#{version} && "\
167
+ "docker tag chef/inspec:#{version} chef/inspec:latest &&"\
168
168
  "docker push chef/inspec:latest"
169
169
  puts "--> #{cmd}"
170
170
  sh('sh', '-c', cmd)
@@ -8,6 +8,6 @@ summary: InSpec Profile that is only consuming dependencies
8
8
  version: 0.2.0
9
9
  depends:
10
10
  - name: hardening/ssh-hardening # defaults to supermarket
11
- - git: https://github.com/dev-sec/ssl-benchmark.git
11
+ - url: https://github.com/dev-sec/ssl-benchmark
12
12
  - name: windows-patch-benchmark
13
- git: https://github.com/chris-rock/windows-patch-benchmark.git
13
+ url: https://github.com/chris-rock/windows-patch-benchmark
@@ -8,12 +8,12 @@ require 'uri'
8
8
  module Compliance
9
9
  # API Implementation does not hold any state by itself,
10
10
  # everything will be stored in local Configuration store
11
- class API
11
+ class API # rubocop:disable Metrics/ClassLength
12
12
  # return all compliance profiles available for the user
13
13
  def self.profiles(config)
14
- url = "#{config['server']}/user/compliance"
15
- # TODO, api should not be dependent on .supported?
16
- response = Compliance::HTTP.get(url, config['token'], config['insecure'], !config.supported?(:oidc))
14
+ config['server_type'] == 'automate' ? url = "#{config['server']}/#{config['user']}" : url = "#{config['server']}/user/compliance"
15
+ headers = get_headers(config)
16
+ response = Compliance::HTTP.get(url, headers, config['insecure'])
17
17
  data = response.body
18
18
  response_code = response.code
19
19
  case response_code
@@ -21,11 +21,17 @@ module Compliance
21
21
  msg = 'success'
22
22
  profiles = JSON.parse(data)
23
23
  # iterate over profiles
24
- mapped_profiles = profiles.map do |owner, ps|
25
- ps.keys.map do |name|
26
- { org: owner, name: name }
27
- end
28
- end.flatten
24
+ if config['server_type'] == 'automate'
25
+ mapped_profiles = profiles.map do |owner, ps|
26
+ { org: ps['owner_id'], name: owner }
27
+ end.flatten
28
+ else
29
+ mapped_profiles = profiles.map do |owner, ps|
30
+ ps.keys.map do |name|
31
+ { org: owner, name: name }
32
+ end
33
+ end.flatten
34
+ end
29
35
  return msg, mapped_profiles
30
36
  when '401'
31
37
  msg = '401 Unauthorized. Please check your token.'
@@ -69,8 +75,9 @@ Please login using `inspec compliance login https://compliance.test --user admin
69
75
 
70
76
  def self.upload(config, owner, profile_name, archive_path)
71
77
  # upload the tar to Chef Compliance
72
- url = "#{config['server']}/owners/#{owner}/compliance/#{profile_name}/tar"
73
- res = Compliance::HTTP.post_file(url, config['token'], archive_path, config['insecure'], !config.supported?(:oidc))
78
+ config['server_type'] == 'automate' ? url = "#{config['server']}/#{config['user']}" : url = "#{config['server']}/owners/#{owner}/compliance/#{profile_name}/tar"
79
+ headers = get_headers(config)
80
+ res = Compliance::HTTP.post_file(url, headers, archive_path, config['insecure'])
74
81
  [res.is_a?(Net::HTTPSuccess), res.body]
75
82
  end
76
83
 
@@ -121,5 +128,20 @@ Please login using `inspec compliance login https://compliance.test --user admin
121
128
 
122
129
  [success, msg, access_token]
123
130
  end
131
+
132
+ def self.get_headers(config)
133
+ if config['server_type'] == 'automate'
134
+ headers = { 'chef-delivery-enterprise' => config['automate']['ent'] }
135
+ if config['automate']['token_type'] == 'dctoken'
136
+ headers['x-data-collector-token'] = config['token']
137
+ else
138
+ headers['chef-delivery-user'] = config['user']
139
+ headers['chef-delivery-token'] = config['token']
140
+ end
141
+ else
142
+ headers = { 'Authorization' => "Bearer #{config['token']}" }
143
+ end
144
+ headers
145
+ end
124
146
  end
125
147
  end
@@ -36,6 +36,7 @@ module Compliance
36
36
 
37
37
  options['server'] = server
38
38
  url = options['server'] + options['apipath']
39
+
39
40
  if !options['user'].nil? && !options['password'].nil?
40
41
  # username / password
41
42
  _success, msg = login_username_password(url, options['user'], options['password'], options['insecure'])
@@ -56,6 +57,35 @@ module Compliance
56
57
  puts '', msg
57
58
  end
58
59
 
60
+ desc "login_automate SERVER --user='USER' --ent='ENT' --dctoken or --usertoken='TOKEN'", 'Log in to an Automate SERVER'
61
+ option :dctoken, type: :string,
62
+ desc: 'Data Collector token'
63
+ option :usertoken, type: :string,
64
+ desc: 'Automate user token'
65
+ option :user, type: :string,
66
+ desc: 'Automate username'
67
+ option :ent, type: :string,
68
+ desc: 'Enterprise for Chef Automate reporting'
69
+ option :insecure, aliases: :k, type: :boolean,
70
+ desc: 'Explicitly allows InSpec to perform "insecure" SSL connections and transfers'
71
+ def login_automate(server) # rubocop:disable Metrics/AbcSize
72
+ options['server'] = server
73
+ url = options['server'] + '/compliance/profiles'
74
+
75
+ if url && !options['user'].nil? && !options['ent'].nil?
76
+ if !options['dctoken'].nil? || !options['usertoken'].nil?
77
+ msg = login_automate_config(url, options['user'], options['dctoken'], options['usertoken'], options['ent'], options['insecure'])
78
+ else
79
+ puts "Please specify a token using --dctoken='DATA_COLLECTOR_TOKEN' or usertoken='AUTOMATE_TOKEN' "
80
+ exit 1
81
+ end
82
+ else
83
+ puts "Please login to your automate instance using 'inspec compliance login_automate SERVER --user AUTOMATE_USER --ent AUTOMATE_ENT --dctoken DC_TOKEN or --usertoken USER_TOKEN' "
84
+ exit 1
85
+ end
86
+ puts '', msg
87
+ end
88
+
59
89
  desc 'profiles', 'list all available profiles in Chef Compliance'
60
90
  def profiles
61
91
  config = Compliance::Configuration.new
@@ -79,10 +109,8 @@ module Compliance
79
109
  def exec(*tests)
80
110
  config = Compliance::Configuration.new
81
111
  return if !loggedin(config)
82
-
83
112
  # iterate over tests and add compliance scheme
84
113
  tests = tests.map { |t| 'compliance://' + t }
85
-
86
114
  # execute profile from inspec exec implementation
87
115
  diagnose
88
116
  run_tests(tests, opts)
@@ -100,6 +128,8 @@ module Compliance
100
128
  exit 1
101
129
  end
102
130
 
131
+ vendor_deps(path, options) if File.directory?(path)
132
+
103
133
  o = options.dup
104
134
  configure_logger(o)
105
135
  # check the profile, we only allow to upload valid profiles
@@ -144,7 +174,6 @@ module Compliance
144
174
  # if it is a directory, tar it to tmp directory
145
175
  if File.directory?(path)
146
176
  archive_path = Dir::Tmpname.create([profile_name, '.tar.gz']) {}
147
- # archive_path = file.path
148
177
  puts "Generate temporary profile archive at #{archive_path}"
149
178
  profile.archive({ output: archive_path, ignore_errors: false, overwrite: true })
150
179
  else
@@ -154,7 +183,8 @@ module Compliance
154
183
  puts "Start upload to #{owner}/#{profile_name}"
155
184
  pname = ERB::Util.url_encode(profile_name)
156
185
 
157
- puts 'Uploading to Chef Compliance'
186
+ config['server_type'] == 'automate' ? upload_msg = 'Uploading to Chef Automate' : upload_msg = 'Uploading to Chef Compliance'
187
+ puts upload_msg
158
188
  success, msg = Compliance::API.upload(config, owner, pname, archive_path)
159
189
 
160
190
  if success
@@ -169,24 +199,27 @@ module Compliance
169
199
  desc 'version', 'displays the version of the Chef Compliance server'
170
200
  def version
171
201
  config = Compliance::Configuration.new
172
- info = Compliance::API.version(config['server'], config['insecure'])
173
- if !info.nil? && info['version']
174
- puts "Chef Compliance version: #{info['version']}"
202
+ if config['server_type'] == 'automate'
203
+ puts 'Version not available when logged in with Automate.'
175
204
  else
176
- puts 'Could not determine server version.'
177
- exit 1
205
+ info = Compliance::API.version(config['server'], config['insecure'])
206
+ if !info.nil? && info['version']
207
+ puts "Chef Compliance version: #{info['version']}"
208
+ else
209
+ puts 'Could not determine server version.'
210
+ exit 1
211
+ end
178
212
  end
179
213
  end
180
214
 
181
215
  desc 'logout', 'user logout from Chef Compliance'
182
216
  def logout
183
217
  config = Compliance::Configuration.new
184
- unless config.supported?(:oidc) || config['token'].nil?
218
+ unless config.supported?(:oidc) || config['token'].nil? || config['server_type'] == 'automate'
185
219
  config = Compliance::Configuration.new
186
220
  url = "#{config['server']}/logout"
187
221
  Compliance::API.post(url, config['token'], config['insecure'], !config.supported?(:oidc))
188
222
  end
189
-
190
223
  success = config.destroy
191
224
 
192
225
  if success
@@ -198,6 +231,32 @@ module Compliance
198
231
 
199
232
  private
200
233
 
234
+ def login_automate_config(url, user, dctoken, usertoken, ent, insecure) # rubocop:disable Metrics/ParameterLists
235
+ config = Compliance::Configuration.new
236
+ config['user'] = user
237
+ config['server'] = url
238
+ config['automate'] = {}
239
+ config['automate']['ent'] = ent
240
+ config['server_type'] = 'automate'
241
+ config['insecure'] = insecure
242
+
243
+ # determine token method being used
244
+ if !dctoken.nil?
245
+ config['token'] = dctoken
246
+ token_type = 'dctoken'
247
+ token_msg = 'data collector token'
248
+ else
249
+ config['token'] = usertoken
250
+ token_type = 'usertoken'
251
+ token_msg = 'automate user token'
252
+ end
253
+
254
+ config['automate']['token_type'] = token_type
255
+ config.store
256
+ msg = "Stored configuration for Chef Automate: '#{url}' with user: '#{user}', ent: '#{ent}' and your #{token_msg}"
257
+ msg
258
+ end
259
+
201
260
  def login_refreshtoken(url, options)
202
261
  success, msg, access_token = Compliance::API.get_token_via_refresh_token(url, options['refresh_token'], options['insecure'])
203
262
  if success
@@ -206,6 +265,7 @@ module Compliance
206
265
  config['token'] = access_token
207
266
  config['insecure'] = options['insecure']
208
267
  config['version'] = Compliance::API.version(url, options['insecure'])
268
+ config['server_type'] = 'compliance'
209
269
  config.store
210
270
  end
211
271
 
@@ -221,6 +281,7 @@ module Compliance
221
281
  config['token'] = api_token
222
282
  config['insecure'] = insecure
223
283
  config['version'] = Compliance::API.version(url, insecure)
284
+ config['server_type'] = 'compliance'
224
285
  config.store
225
286
  success = true
226
287
  end
@@ -267,7 +328,7 @@ module Compliance
267
328
 
268
329
  def loggedin(config)
269
330
  serverknown = !config['server'].nil?
270
- puts 'You need to login first with `inspec compliance login`' if !serverknown
331
+ puts 'You need to login first with `inspec compliance login` or `inspec compliance login_automate`' if !serverknown
271
332
  serverknown
272
333
  end
273
334
  end
@@ -9,16 +9,13 @@ module Compliance
9
9
  # implements a simple http abstraction on top of Net::HTTP
10
10
  class HTTP
11
11
  # generic get requires
12
- def self.get(url, token, insecure, basic_auth = false)
12
+ def self.get(url, headers = nil, insecure)
13
13
  uri = URI.parse(url)
14
14
  req = Net::HTTP::Get.new(uri.path)
15
-
16
- return send_request(uri, req, insecure) if token.nil?
17
-
18
- if basic_auth
19
- req.basic_auth(token, '')
20
- else
21
- req['Authorization'] = "Bearer #{token}"
15
+ if !headers.nil?
16
+ headers.each do |key, value|
17
+ req.add_field(key, value)
18
+ end
22
19
  end
23
20
  send_request(uri, req, insecure)
24
21
  end
@@ -39,7 +36,7 @@ module Compliance
39
36
  end
40
37
 
41
38
  # post a file
42
- def self.post_file(url, token, file_path, insecure, basic_auth = false)
39
+ def self.post_file(url, headers, file_path, insecure)
43
40
  uri = URI.parse(url)
44
41
  fail "Unable to parse URL: #{url}" if uri.nil? || uri.host.nil?
45
42
  http = Net::HTTP.new(uri.host, uri.port)
@@ -49,10 +46,8 @@ module Compliance
49
46
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE if insecure
50
47
 
51
48
  req = Net::HTTP::Post.new(uri.path)
52
- if basic_auth
53
- req.basic_auth token, ''
54
- else
55
- req['Authorization'] = "Bearer #{token}"
49
+ headers.each do |key, value|
50
+ req.add_field(key, value)
56
51
  end
57
52
 
58
53
  req.body_stream=File.open(file_path, 'rb')
@@ -13,8 +13,7 @@ module Compliance
13
13
  class Fetcher < Fetchers::Url
14
14
  name 'compliance'
15
15
  priority 500
16
-
17
- def self.resolve(target)
16
+ def self.resolve(target) # rubocop:disable PerceivedComplexity
18
17
  uri = if target.is_a?(String) && URI(target).scheme == 'compliance'
19
18
  URI(target)
20
19
  elsif target.respond_to?(:key?) && target.key?(:compliance)
@@ -26,14 +25,21 @@ module Compliance
26
25
  # check if we have a compliance token
27
26
  config = Compliance::Configuration.new
28
27
  if config['token'].nil?
28
+ if config['server_type'] == 'automate'
29
+ server = 'automate'
30
+ msg = 'inspec compliance login_automate https://your_automate_server --user USER --ent ENT --dctoken DCTOKEN or --usertoken USERTOKEN'
31
+ else
32
+ server = 'compliance'
33
+ msg = "inspec compliance login https://your_compliance_server --user admin --insecure --token 'PASTE TOKEN HERE' "
34
+ end
29
35
  fail Inspec::FetcherFailure, <<EOF
30
36
 
31
- Cannot fetch #{uri} because your compliance token has not been
37
+ Cannot fetch #{uri} because your #{server} token has not been
32
38
  configured.
33
39
 
34
40
  Please login using
35
41
 
36
- inspec compliance login https://your_compliance_server --user admin --insecure --token 'PASTE TOKEN HERE'
42
+ #{msg}
37
43
  EOF
38
44
  end
39
45
 
@@ -48,8 +54,13 @@ EOF
48
54
  end
49
55
 
50
56
  def self.target_url(profile, config)
51
- owner, id = profile.split('/')
52
- "#{config['server']}/owners/#{owner}/compliance/#{id}/tar"
57
+ if config['server_type'] == 'automate'
58
+ target = "#{config['server']}/#{profile}/tar"
59
+ else
60
+ owner, id = profile.split('/')
61
+ target = "#{config['server']}/owners/#{owner}/compliance/#{id}/tar"
62
+ end
63
+ target
53
64
  end
54
65
 
55
66
  #
@@ -8,7 +8,7 @@ require 'tempfile'
8
8
  require 'open-uri'
9
9
 
10
10
  module Fetchers
11
- class Url < Inspec.fetcher(1)
11
+ class Url < Inspec.fetcher(1) # rubocop:disable Metrics/ClassLength
12
12
  MIME_TYPES = {
13
13
  'application/x-zip-compressed' => '.zip',
14
14
  'application/zip' => '.zip',
@@ -126,7 +126,19 @@ module Fetchers
126
126
  Inspec::Log.debug("Fetching URL: #{@target}")
127
127
  http_opts = {}
128
128
  http_opts['ssl_verify_mode'.to_sym] = OpenSSL::SSL::VERIFY_NONE if @insecure
129
- http_opts['Authorization'] = "Bearer #{@token}" if @token
129
+ if @config
130
+ if @config['server_type'] == 'automate'
131
+ http_opts['chef-delivery-enterprise'] = @config['automate']['ent']
132
+ if @config['automate']['token_type'] == 'dctoken'
133
+ http_opts['x-data-collector-token'] = @config['token']
134
+ else
135
+ http_opts['chef-delivery-user'] = @config['user']
136
+ http_opts['chef-delivery-token'] = @config['token']
137
+ end
138
+ elsif @token
139
+ http_opts['Authorization'] = "Bearer #{@token}"
140
+ end
141
+ end
130
142
  remote = open(@target, http_opts)
131
143
  @archive_type = file_type_from_remote(remote) # side effect :(
132
144
  archive = Tempfile.new(['inspec-dl-', @archive_type])
@@ -57,7 +57,7 @@ module Inspec
57
57
  option :controls, type: :array,
58
58
  desc: 'A list of controls to run. Ignore all other tests.'
59
59
  option :format, type: :string,
60
- desc: 'Which formatter to use: cli, progress, documentation, json, json-min'
60
+ desc: 'Which formatter to use: cli, progress, documentation, json, json-min, junit'
61
61
  option :color, type: :boolean, default: true,
62
62
  desc: 'Use colors in output.'
63
63
  option :attrs, type: :array,
@@ -146,6 +146,35 @@ module Inspec
146
146
  end
147
147
  end
148
148
 
149
+ def vendor_deps(path, opts)
150
+ path.nil? ? path = Pathname.new(Dir.pwd) : path = Pathname.new(path)
151
+ cache_path = path.join('vendor')
152
+ inspec_lock = path.join('inspec.lock')
153
+
154
+ if (cache_path.exist? || inspec_lock.exist?) && !opts[:overwrite]
155
+ puts 'Profile is already vendored. Use --overwrite.'
156
+ return false
157
+ end
158
+
159
+ # remove existing
160
+ FileUtils.rm_rf(cache_path) if cache_path.exist?
161
+ File.delete(inspec_lock) if inspec_lock.exist?
162
+
163
+ puts "Vendor dependencies of #{path} into #{cache_path}"
164
+ opts[:logger] = Logger.new(STDOUT)
165
+ opts[:logger].level = get_log_level(opts.log_level)
166
+ opts[:cache] = Inspec::Cache.new(cache_path.to_s)
167
+ opts[:backend] = Inspec::Backend.create(target: 'mock://')
168
+ configure_logger(opts)
169
+
170
+ # vendor dependencies and generate lockfile
171
+ profile = Inspec::Profile.for_target(path.to_s, opts)
172
+ lockfile = profile.generate_lockfile
173
+ File.write(inspec_lock, lockfile.to_yaml)
174
+ rescue StandardError => e
175
+ pretty_handle_exception(e)
176
+ end
177
+
149
178
  def configure_logger(o)
150
179
  #
151
180
  # TODO(ssd): This is a big gross, but this configures the
@@ -33,6 +33,7 @@ class Inspec::InspecCLI < Inspec::BaseCLI # rubocop:disable Metrics/ClassLength
33
33
  def json(target)
34
34
  diagnose
35
35
  o = opts.dup
36
+ configure_logger(o)
36
37
  o[:ignore_supports] = true
37
38
  o[:backend] = Inspec::Backend.create(target: 'mock://')
38
39
 
@@ -59,7 +60,7 @@ class Inspec::InspecCLI < Inspec::BaseCLI # rubocop:disable Metrics/ClassLength
59
60
  def check(path) # rubocop:disable Metrics/AbcSize
60
61
  diagnose
61
62
  o = opts.dup
62
- # configure_logger(o) # we do not need a logger for check yet
63
+ configure_logger(o)
63
64
  o[:ignore_supports] = true # we check for integrity only
64
65
  o[:backend] = Inspec::Backend.create(target: 'mock://')
65
66
 
@@ -108,35 +109,9 @@ class Inspec::InspecCLI < Inspec::BaseCLI # rubocop:disable Metrics/ClassLength
108
109
  desc 'vendor PATH', 'Download all dependencies and generate a lockfile in a `vendor` directory'
109
110
  option :overwrite, type: :boolean, default: false,
110
111
  desc: 'Overwrite existing vendored dependencies and lockfile.'
111
- def vendor(path = nil) # rubocop:disable Metrics/AbcSize
112
+ def vendor(path = nil)
112
113
  o = opts.dup
113
-
114
- path.nil? ? path = Pathname.new(Dir.pwd) : path = Pathname.new(path)
115
- cache_path = path.join('vendor')
116
- inspec_lock = path.join('inspec.lock')
117
-
118
- if (cache_path.exist? || inspec_lock.exist?) && !opts[:overwrite]
119
- puts 'Profile is already vendored. Use --overwrite.'
120
- return false
121
- end
122
-
123
- # remove existing
124
- FileUtils.rm_rf(cache_path) if cache_path.exist?
125
- File.delete(inspec_lock) if inspec_lock.exist?
126
-
127
- puts "Vendor dependencies of #{path} into #{cache_path}"
128
- o[:logger] = Logger.new(STDOUT)
129
- o[:logger].level = get_log_level(o.log_level)
130
- o[:cache] = Inspec::Cache.new(cache_path.to_s)
131
- o[:backend] = Inspec::Backend.create(target: 'mock://')
132
- configure_logger(o)
133
-
134
- # vendor dependencies and generate lockfile
135
- profile = Inspec::Profile.for_target(path.to_s, o)
136
- lockfile = profile.generate_lockfile
137
- File.write(inspec_lock, lockfile.to_yaml)
138
- rescue StandardError => e
139
- pretty_handle_exception(e)
114
+ vendor_deps(path, o)
140
115
  end
141
116
 
142
117
  desc 'archive PATH', 'archive a profile to tar.gz (default) or zip'
@@ -211,7 +186,7 @@ class Inspec::InspecCLI < Inspec::BaseCLI # rubocop:disable Metrics/ClassLength
211
186
  option :command, aliases: :c,
212
187
  desc: 'A single command string to run instead of launching the shell'
213
188
  option :format, type: :string, default: nil, hide: true,
214
- desc: 'Which formatter to use: cli, progress, documentation, json, json-min'
189
+ desc: 'Which formatter to use: cli, progress, documentation, json, json-min, junit'
215
190
  def shell_func
216
191
  diagnose
217
192
  o = opts.dup
@@ -15,14 +15,19 @@ module Inspec
15
15
  new(lockfile_content)
16
16
  end
17
17
 
18
- def self.from_file(path)
19
- parsed_content = YAML.load(File.read(path))
18
+ def self.from_content(content)
19
+ parsed_content = YAML.load(content)
20
20
  version = parsed_content['lockfile_version']
21
21
  fail "No lockfile_version set in #{path}!" if version.nil?
22
22
  validate_lockfile_version!(version.to_i)
23
23
  new(parsed_content)
24
24
  end
25
25
 
26
+ def self.from_file(path)
27
+ content = File.read(path)
28
+ from_content(content)
29
+ end
30
+
26
31
  def self.validate_lockfile_version!(version)
27
32
  if version < MINIMUM_SUPPORTED_VERSION
28
33
  fail <<EOF
@@ -21,10 +21,9 @@ module Inspec
21
21
  class Profile # rubocop:disable Metrics/ClassLength
22
22
  extend Forwardable
23
23
 
24
- def self.resolve_target(target, cache = nil)
25
- c = cache || Cache.new
26
- Inspec::Log.debug "Resolve #{target} into cache #{c.path}"
27
- Inspec::CachedFetcher.new(target, cache || Cache.new)
24
+ def self.resolve_target(target, cache)
25
+ Inspec::Log.debug "Resolve #{target} into cache #{cache.path}"
26
+ Inspec::CachedFetcher.new(target, cache)
28
27
  end
29
28
 
30
29
  # Check if the profile contains a vendored cache, move content into global cache
@@ -65,11 +64,13 @@ module Inspec
65
64
  end
66
65
 
67
66
  def self.for_fetcher(fetcher, opts)
67
+ opts[:cache] = opts[:cache] || Cache.new
68
68
  path, writable = fetcher.fetch
69
69
  for_path(path, opts.merge(target: fetcher.target, writable: writable))
70
70
  end
71
71
 
72
72
  def self.for_target(target, opts = {})
73
+ opts[:cache] = opts[:cache] || Cache.new
73
74
  fetcher = resolve_target(target, opts[:cache])
74
75
  for_fetcher(fetcher, opts)
75
76
  end
@@ -205,7 +206,7 @@ module Inspec
205
206
  res
206
207
  end
207
208
 
208
- # Check if the profile is internall well-structured. The logger will be
209
+ # Check if the profile is internally well-structured. The logger will be
209
210
  # used to print information on errors and warnings which are found.
210
211
  #
211
212
  # @return [Boolean] true if no errors were found, false otherwise
@@ -342,7 +343,7 @@ module Inspec
342
343
  end
343
344
 
344
345
  def lockfile_exists?
345
- File.exist?(lockfile_path)
346
+ @source_reader.target.files.include?('inspec.lock')
346
347
  end
347
348
 
348
349
  def lockfile_path
@@ -360,7 +361,7 @@ module Inspec
360
361
 
361
362
  def lockfile
362
363
  @lockfile ||= if lockfile_exists?
363
- Inspec::Lockfile.from_file(lockfile_path)
364
+ Inspec::Lockfile.from_content(@source_reader.target.read('inspec.lock'))
364
365
  else
365
366
  generate_lockfile
366
367
  end
@@ -280,13 +280,7 @@ class InspecRspecCli < InspecRspecJson # rubocop:disable Metrics/ClassLength
280
280
  MULTI_TEST_CONTROL_SUMMARY_MAX_LEN = 60
281
281
 
282
282
  def initialize(*args)
283
- @colors = COLORS
284
- @indicators = INDICATORS
285
-
286
- @format = '%color%indicator%id%summary'
287
283
  @current_control = nil
288
- @current_profile = nil
289
- @missing_controls = []
290
284
  @anonymous_tests = []
291
285
  @control_tests = []
292
286
  @profile_printed = false
@@ -294,12 +288,12 @@ class InspecRspecCli < InspecRspecJson # rubocop:disable Metrics/ClassLength
294
288
  end
295
289
 
296
290
  def close(_notification) # rubocop:disable Metrics/AbcSize
297
- flush_current_control
291
+ flush_current_control(@current_control)
298
292
  output.puts('') unless @current_control.nil?
299
- print_tests
293
+ print_tests(@anonymous_tests)
300
294
  output.puts('')
301
295
 
302
- print_profiles_info if !@profile_printed
296
+ print_profiles_info(@current_control) if !@profile_printed
303
297
  controls_res = controls_summary
304
298
  tests_res = tests_summary
305
299
 
@@ -318,6 +312,31 @@ class InspecRspecCli < InspecRspecJson # rubocop:disable Metrics/ClassLength
318
312
 
319
313
  private
320
314
 
315
+ # Formats example; calls example2control, gets a 'status_type', and calls
316
+ # flush current_control; returns control data
317
+ def format_example(example)
318
+ data = super(example)
319
+ control = example2control(data, @profiles_info) || {}
320
+ control[:id] = data[:id]
321
+ control[:profile_id] = data[:profile_id]
322
+
323
+ data[:status_type] = status_type(data, control)
324
+ dump_one_example(data, control)
325
+
326
+ @current_control ||= control
327
+ if !control[:id].nil?
328
+ if @current_control[:id] != control[:id]
329
+ flush_current_control(@current_control)
330
+ @current_control = control
331
+ end
332
+ end
333
+
334
+ data
335
+ end
336
+
337
+ # Determines 'status_type' (critical, major, minor) of control given
338
+ # status (failed/passed/skipped) and impact value (0.0 - 1.0).
339
+ # Called from format_example, sets the 'status_type' for each 'example'
321
340
  def status_type(data, control)
322
341
  status = data[:status]
323
342
  return status if status != 'failed' || control[:impact].nil?
@@ -330,12 +349,45 @@ class InspecRspecCli < InspecRspecJson # rubocop:disable Metrics/ClassLength
330
349
  end
331
350
  end
332
351
 
333
- def current_control_infos
352
+ # TODO: does a lot of stuff!
353
+ # Called from format_example and close
354
+ def flush_current_control(current_control)
355
+ return if current_control.nil?
356
+
357
+ profile = @profiles_info.find { |i| i[:id] == current_control[:profile_id] }
358
+ print_current_profile(profile) if !@profile_printed
359
+
360
+ fails, skips, passes, summary_indicator = current_control_infos(current_control)
361
+ summary = current_control_summary(fails, skips, current_control)
362
+
363
+ control_id = current_control[:id].to_s
364
+ control_id += ': '
365
+ if control_id.start_with? '(generated from '
366
+ @anonymous_tests.push(current_control)
367
+ else
368
+ @control_tests.push(current_control)
369
+ print_line(
370
+ color: COLORS[summary_indicator] || '',
371
+ indicator: INDICATORS[summary_indicator] || INDICATORS['unknown'],
372
+ summary: format_lines(summary, INDICATORS['empty']),
373
+ id: control_id,
374
+ profile: current_control[:profile_id],
375
+ )
376
+
377
+ print_results(fails + skips + passes)
378
+ end
379
+ end
380
+
381
+ ############# Current control methods #############
382
+
383
+ # Takes current_control (called from flush_current_control) and returns
384
+ # fails, skips, passes.
385
+ def current_control_infos(current_control)
334
386
  summary_status = STATUS_TYPES['unknown']
335
387
  skips = []
336
388
  fails = []
337
389
  passes = []
338
- @current_control[:results].each do |r|
390
+ current_control[:results].each do |r|
339
391
  i = STATUS_TYPES[r[:status_type]]
340
392
  summary_status = i if i > summary_status
341
393
  fails.push(r) if i > 0
@@ -345,9 +397,11 @@ class InspecRspecCli < InspecRspecJson # rubocop:disable Metrics/ClassLength
345
397
  [fails, skips, passes, STATUS_TYPES.key(summary_status)]
346
398
  end
347
399
 
348
- def current_control_title
349
- title = @current_control[:title]
350
- res = @current_control[:results]
400
+ # Determine title for control given current_control.
401
+ # Called from current_control_summary.
402
+ def current_control_title(current_control)
403
+ title = current_control[:title]
404
+ res = current_control[:results]
351
405
  if title
352
406
  title
353
407
  elsif res.length == 1
@@ -368,9 +422,10 @@ class InspecRspecCli < InspecRspecJson # rubocop:disable Metrics/ClassLength
368
422
  end
369
423
  end
370
424
 
371
- def current_control_summary(fails, skips)
372
- title = current_control_title
373
- res = @current_control[:results]
425
+ # Return summary of current_control, called from flush_current_control
426
+ def current_control_summary(fails, skips, current_control)
427
+ title = current_control_title(current_control)
428
+ res = current_control[:results]
374
429
  suffix =
375
430
  if res.length == 1
376
431
  # Single test - be nice and just print the exception message if the test
@@ -389,27 +444,34 @@ class InspecRspecCli < InspecRspecJson # rubocop:disable Metrics/ClassLength
389
444
  end
390
445
  end
391
446
 
447
+ ############# Print results and lines methods #############
448
+
449
+ # Formats the line (called from print_line)
392
450
  def format_line(fields)
393
- @format.gsub(/%\w+/) do |x|
451
+ format = '%color%indicator%id%summary'
452
+ format.gsub(/%\w+/) do |x|
394
453
  term = x[1..-1]
395
454
  fields.key?(term.to_sym) ? fields[term.to_sym].to_s : x
396
- end + @colors['reset']
455
+ end + COLORS['reset']
397
456
  end
398
457
 
458
+ # Prints line; used to print results
399
459
  def print_line(fields)
400
460
  output.puts(format_line(fields))
401
461
  end
402
462
 
463
+ # Helps formatting summary lines (called from within print_line arguments)
403
464
  def format_lines(lines, indentation)
404
465
  lines.gsub(/\n/, "\n" + indentation)
405
466
  end
406
467
 
468
+ # Sorts through results, calls print_line (called from flush_current_control)
407
469
  def print_results(all)
408
470
  all.each do |x|
409
471
  test_status = x[:status_type]
410
- test_color = @colors[test_status]
411
- indicator = @indicators[x[:status]]
412
- indicator = @indicators['empty'] if indicator.nil?
472
+ test_color = COLORS[test_status]
473
+ indicator = INDICATORS[x[:status]]
474
+ indicator = INDICATORS['empty'] if indicator.nil?
413
475
  if x[:message]
414
476
  msg = x[:code_desc] + "\n" + x[:message]
415
477
  else
@@ -417,15 +479,16 @@ class InspecRspecCli < InspecRspecJson # rubocop:disable Metrics/ClassLength
417
479
  end
418
480
  print_line(
419
481
  color: test_color,
420
- indicator: @indicators['small'] + indicator,
421
- summary: format_lines(msg, @indicators['empty']),
482
+ indicator: INDICATORS['small'] + indicator,
483
+ summary: format_lines(msg, INDICATORS['empty']),
422
484
  id: nil, profile: nil
423
485
  )
424
486
  end
425
487
  end
426
488
 
427
- def print_tests # rubocop:disable Metrics/AbcSize
428
- @anonymous_tests.each do |control|
489
+ # Prints anonymous describe blocks; called from close method
490
+ def print_tests(anonymous_tests) # rubocop:disable Metrics/AbcSize
491
+ anonymous_tests.each do |control|
429
492
  control_result = control[:results]
430
493
  title = control_result[0][:code_desc].split[0..1].join(' ')
431
494
  puts ' ' + title
@@ -443,9 +506,9 @@ class InspecRspecCli < InspecRspecJson # rubocop:disable Metrics/ClassLength
443
506
  end
444
507
  status_indicator = test[:status_type]
445
508
  print_line(
446
- color: @colors[status_indicator] || '',
447
- indicator: @indicators['small'] + @indicators[status_indicator] || @indicators['unknown'],
448
- summary: format_lines(test_result, @indicators['empty']),
509
+ color: COLORS[status_indicator] || '',
510
+ indicator: INDICATORS['small'] + INDICATORS[status_indicator] || INDICATORS['unknown'],
511
+ summary: format_lines(test_result, INDICATORS['empty']),
449
512
  id: control_id,
450
513
  profile: control[:profile_id],
451
514
  )
@@ -453,57 +516,33 @@ class InspecRspecCli < InspecRspecJson # rubocop:disable Metrics/ClassLength
453
516
  end
454
517
  end
455
518
 
456
- def flush_current_control
457
- return if @current_control.nil?
458
-
459
- @current_profile = @profiles_info.find { |i| i[:id] == @current_control[:profile_id] }
460
- print_current_profile if !@profile_printed
461
-
462
- fails, skips, passes, summary_indicator = current_control_infos
463
- summary = current_control_summary(fails, skips)
464
-
465
- control_id = @current_control[:id].to_s
466
- control_id += ': '
467
- if control_id.start_with? '(generated from '
468
- @anonymous_tests.push(@current_control)
469
- else
470
- @control_tests.push(@current_control)
471
- print_line(
472
- color: @colors[summary_indicator] || '',
473
- indicator: @indicators[summary_indicator] || @indicators['unknown'],
474
- summary: format_lines(summary, @indicators['empty']),
475
- id: control_id,
476
- profile: @current_control[:profile_id],
477
- )
478
-
479
- print_results(fails + skips + passes)
480
- end
481
- end
519
+ ############# Print profile info methods #############
482
520
 
483
- def print_target(before, after)
521
+ # Prints target information; called from print_current_profile
522
+ def print_target
484
523
  return if @backend.nil?
485
524
  connection = @backend.backend
486
525
  return unless connection.respond_to?(:uri)
487
- output.puts(before + connection.uri + after)
526
+ output.puts('Target: ' + connection.uri + "\n\n")
488
527
  end
489
528
 
490
- def print_profiles_info
529
+ # Prints blank info is no current_control is defined
530
+ # Called from print_current_profile and close
531
+ def print_profiles_info(current_control)
491
532
  @profiles_info.each do |profile|
492
533
  next if profile[:already_printed]
493
- @current_profile = profile
494
- next unless print_current_profile
534
+ next unless print_current_profile(profile)
495
535
  print_line(
496
- color: '', indicator: @indicators['empty'], id: '', profile: '',
536
+ color: '', indicator: INDICATORS['empty'], id: '', profile: '',
497
537
  summary: 'No tests executed.'
498
- ) if @current_control.nil?
538
+ ) if current_control.nil?
499
539
  output.puts('')
500
540
  end
501
541
  end
502
542
 
503
- def print_current_profile
504
- profile = @current_profile
543
+ def print_current_profile(profile)
505
544
  if profile.nil?
506
- print_profiles_info
545
+ print_profiles_info(@current_control)
507
546
  @profile_printed = true
508
547
  return true
509
548
  end
@@ -511,7 +550,7 @@ class InspecRspecCli < InspecRspecJson # rubocop:disable Metrics/ClassLength
511
550
  profile[:already_printed] = true
512
551
 
513
552
  if profile[:name].nil?
514
- print_target('Target: ', "\n\n")
553
+ print_target
515
554
  @profile_printed = true
516
555
  return true
517
556
  end
@@ -523,31 +562,10 @@ class InspecRspecCli < InspecRspecJson # rubocop:disable Metrics/ClassLength
523
562
  end
524
563
 
525
564
  output.puts 'Version: ' + (profile[:version] || 'unknown')
526
- print_target('Target: ', "\n")
527
- output.puts
565
+ print_target
528
566
  @profile_printed = true
529
567
  true
530
568
  end
531
-
532
- def format_example(example)
533
- data = super(example)
534
- control = example2control(data, @profiles_info) || {}
535
- control[:id] = data[:id]
536
- control[:profile_id] = data[:profile_id]
537
-
538
- data[:status_type] = status_type(data, control)
539
- dump_one_example(data, control)
540
-
541
- @current_control ||= control
542
- if control[:id].nil?
543
- @missing_controls.push(data)
544
- elsif @current_control[:id] != control[:id]
545
- flush_current_control
546
- @current_control = control
547
- end
548
-
549
- data
550
- end
551
569
  end
552
570
 
553
571
  class InspecRspecJUnit < RSpecJUnitFormatter
@@ -4,5 +4,5 @@
4
4
  # author: Christoph Hartmann
5
5
 
6
6
  module Inspec
7
- VERSION = '1.6.0'.freeze
7
+ VERSION = '1.7.0'.freeze
8
8
  end
@@ -21,9 +21,10 @@ module Inspec::Resources
21
21
  :states
22
22
 
23
23
  def initialize(grep)
24
+ @grep = grep
24
25
  # turn into a regexp if it isn't one yet
25
26
  if grep.class == String
26
- grep = '(/[^/]*)*'+grep if grep[0] != '/'
27
+ grep = '(/[^/]*)*' + grep if grep[0] != '/'
27
28
  grep = Regexp.new('^' + grep + '(\s|$)')
28
29
  end
29
30
  all_cmds = ps_axo
@@ -38,7 +39,7 @@ module Inspec::Resources
38
39
  end
39
40
 
40
41
  def to_s
41
- 'Processes'
42
+ "Processes #{@grep.class == String ? @grep : @grep.inspect}"
42
43
  end
43
44
 
44
45
  private
@@ -48,7 +49,7 @@ module Inspec::Resources
48
49
 
49
50
  if os.linux?
50
51
  command = 'ps axo label,pid,pcpu,pmem,vsz,rss,tty,stat,start,time,user:32,command'
51
- regex = /^([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+(.*)$/
52
+ regex = /^([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+(\w{3} \d{2}|\d{2}:\d{2}:\d{2})\s+([^ ]+)\s+([^ ]+)\s+(.*)$/
52
53
  else
53
54
  command = 'ps axo pid,pcpu,pmem,vsz,rss,tty,stat,start,time,user,command'
54
55
  regex = /^\s*([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+(.*)$/
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inspec
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.0
4
+ version: 1.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dominik Richter
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-11-28 00:00:00.000000000 Z
11
+ date: 2016-12-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: train