inspec 1.6.0 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
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