inspec 0.35.0 → 1.0.0.beta2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +83 -2
- data/Gemfile +6 -0
- data/Rakefile +3 -55
- data/docs/README.md +20 -0
- data/docs/cli.rst +6 -0
- data/docs/dsl_inspec.md +245 -0
- data/docs/dsl_resource.md +93 -0
- data/docs/inspec_and_friends.md +102 -0
- data/docs/matchers.md +136 -0
- data/docs/plugin_kitchen_inspec.html.md +55 -0
- data/docs/profiles.md +271 -0
- data/docs/resources.rst +1 -1
- data/docs/shell.md +150 -0
- data/inspec.gemspec +1 -1
- data/lib/bundles/inspec-compliance/api.rb +28 -18
- data/lib/bundles/inspec-compliance/cli.rb +19 -27
- data/lib/fetchers/git.rb +4 -0
- data/lib/fetchers/local.rb +16 -1
- data/lib/fetchers/mock.rb +4 -0
- data/lib/fetchers/url.rb +40 -12
- data/lib/inspec/base_cli.rb +4 -0
- data/lib/inspec/cli.rb +6 -8
- data/lib/inspec/control_eval_context.rb +8 -0
- data/lib/inspec/dependencies/{vendor_index.rb → cache.rb} +5 -4
- data/lib/inspec/dependencies/dependency_set.rb +8 -14
- data/lib/inspec/dependencies/requirement.rb +10 -20
- data/lib/inspec/dependencies/resolver.rb +2 -2
- data/lib/inspec/dsl.rb +9 -0
- data/lib/inspec/fetcher.rb +1 -1
- data/lib/inspec/objects/test.rb +8 -2
- data/lib/inspec/plugins/fetcher.rb +11 -12
- data/lib/inspec/plugins/resource.rb +3 -0
- data/lib/inspec/profile.rb +60 -14
- data/lib/inspec/profile_context.rb +28 -7
- data/lib/inspec/resource.rb +17 -2
- data/lib/inspec/rspec_json_formatter.rb +80 -35
- data/lib/inspec/runner.rb +42 -18
- data/lib/inspec/shell.rb +5 -16
- data/lib/inspec/version.rb +1 -1
- data/lib/resources/apache_conf.rb +1 -1
- data/lib/resources/gem.rb +1 -0
- data/lib/resources/oneget.rb +1 -0
- data/lib/resources/os.rb +1 -1
- data/lib/resources/package.rb +3 -1
- data/lib/resources/pip.rb +1 -1
- data/lib/resources/ssl.rb +9 -11
- metadata +15 -15
- data/docs/dsl_inspec.rst +0 -259
- data/docs/dsl_resource.rst +0 -90
- data/docs/inspec_and_friends.rst +0 -85
- data/docs/matchers.rst +0 -137
- data/docs/profiles.rst +0 -169
- data/docs/readme.rst +0 -105
- data/docs/shell.rst +0 -130
- data/docs/template.rst +0 -51
data/docs/resources.rst
CHANGED
@@ -2195,7 +2195,7 @@ This file can be queried via:
|
|
2195
2195
|
|
2196
2196
|
.. code-block:: ruby
|
2197
2197
|
|
2198
|
-
describe json('/
|
2198
|
+
describe json('/path/to/name.json') do
|
2199
2199
|
its('name') { should eq 'hello' }
|
2200
2200
|
its(['meta','creator']) { should eq 'John Doe' }
|
2201
2201
|
its(['array', 1]) { should eq 'one' }
|
data/docs/shell.md
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
---
|
2
|
+
title: InSpec Shell
|
3
|
+
---
|
4
|
+
|
5
|
+
# InSpec Shell
|
6
|
+
|
7
|
+
The InSpec interactive shell is a pry based REPL that can be used to
|
8
|
+
quickly run InSpec controls and tests without having to write it to a
|
9
|
+
file. Its functionality is similar to `chef shell` - it provides a way
|
10
|
+
to exercise the InSpec DSL, its resources, tests and plugins without
|
11
|
+
having to create a profile or write a test file. See
|
12
|
+
[http://pryrepl.org/](http://pryrepl.org/) for an introduction to what pry is and what it can
|
13
|
+
do.
|
14
|
+
|
15
|
+
## Launching the shell
|
16
|
+
|
17
|
+
If you are using InSpec from a platform-specific package (rpm, msi,
|
18
|
+
etc.) or from a chef prepared shell in ChefDK, you can directly launch
|
19
|
+
InSpec shell against your local machine using the following. See
|
20
|
+
<https://docs.chef.io/install_dk.html#set-system-ruby> for details.
|
21
|
+
|
22
|
+
```bash
|
23
|
+
$ inspec shell
|
24
|
+
$ inspec help shell # This will describe inspec shell usage
|
25
|
+
```
|
26
|
+
|
27
|
+
If you wish to connect to a remote machine (called a target within
|
28
|
+
InSpec), you can use the `-t` flag. We support connecting using ssh,
|
29
|
+
WinRm and docker. If no target is provided, we implicitly support the
|
30
|
+
"local" target - i.e. tests running on the current machine running
|
31
|
+
InSpec. For an ssh connection, use `-i` for specifying ssh key files,
|
32
|
+
and the `--sudo*` commands for requesting a privelege escalation after
|
33
|
+
logging in. For a WinRM connection, use `--path` to change the login
|
34
|
+
path, `--ssl` to use SSL for transport layer encryption.
|
35
|
+
|
36
|
+
```bash
|
37
|
+
$ inspec shell -t ssh://root@192.168.64.2:11022 # Login to remote machine using ssh as root.
|
38
|
+
$ inspec shell -t ssh://user@hostname:1234 -i /path/to/user_key # Login to hostname on port 1234 as user using given ssh key.
|
39
|
+
$ inspec shell -t winrm://UserName:Password@windowsmachine:1234 # Login to windowsmachine over WinRM as UserName.
|
40
|
+
$ inspec shell -t docker://container_id # Login to a docker container.
|
41
|
+
```
|
42
|
+
|
43
|
+
## Using Ruby in InSpec shell
|
44
|
+
|
45
|
+
Since InSpec shell is pry based, you may treat the shell as an
|
46
|
+
interactive Ruby session. You may write Ruby expressions and evaluate
|
47
|
+
them. Source high-lighting, automatic indentation and command history
|
48
|
+
(using the up and down arrow keys) are available to make your experience
|
49
|
+
more delightful. You can exit the shell using `exit`.
|
50
|
+
|
51
|
+
```bash
|
52
|
+
$ inspec shell
|
53
|
+
Welcome to the interactive InSpec Shell
|
54
|
+
To find out how to use it, type: help
|
55
|
+
|
56
|
+
inspec> 1 + 2
|
57
|
+
=> 3
|
58
|
+
inspec> exit
|
59
|
+
```
|
60
|
+
|
61
|
+
## Using InSpec DSL in InSpec shell
|
62
|
+
|
63
|
+
InSpec shell will automatically evaluate the result of every command as
|
64
|
+
if it were a test file. If you type in a Ruby command that is not an
|
65
|
+
InSpec control or test, the shell will evaluate it as if it were a
|
66
|
+
regular ruby command.
|
67
|
+
|
68
|
+
Bare InSpec resources are instantiated and their help text is presented.
|
69
|
+
You may also access the resource contents or other matchers that they
|
70
|
+
define. Run `help <resource>` to get more help on using a particular
|
71
|
+
resource or see the InSpec resources documentation online.
|
72
|
+
|
73
|
+
```bash
|
74
|
+
$ inspec shell
|
75
|
+
Welcome to the interactive InSpec Shell
|
76
|
+
To find out how to use it, type: help
|
77
|
+
|
78
|
+
inspec> file('/Users/ksubramanian').directory?
|
79
|
+
=> true
|
80
|
+
inspec> os_env('HOME')
|
81
|
+
=> Environment variable HOME
|
82
|
+
inspec> os_env('HOME').content
|
83
|
+
=> /Users/ksubramanian
|
84
|
+
inspec> exit
|
85
|
+
```
|
86
|
+
|
87
|
+
InSpec tests are immediately executed.
|
88
|
+
|
89
|
+
```bash
|
90
|
+
inspec> describe file('/Users') # Empty test.
|
91
|
+
Summary: 0 successful, 0 failures, 0 skipped
|
92
|
+
inspec> describe file('/Users') do # Test with one check.
|
93
|
+
inspec> it { should exist }
|
94
|
+
inspec> end
|
95
|
+
✔ File /Users should exist
|
96
|
+
|
97
|
+
Summary: 1 successful, 0 failures, 0 skipped
|
98
|
+
```
|
99
|
+
|
100
|
+
All tests in a control are immediately executed as well. If a control is
|
101
|
+
redefined in the shell, the old control's tests are destroyed and
|
102
|
+
replaced with the redefinition and the control is re-run.
|
103
|
+
|
104
|
+
```bash
|
105
|
+
inspec> control 'my_control' do
|
106
|
+
inspec> describe os_env('HOME') do
|
107
|
+
inspec> its('content') { should eq '/Users/ksubramanian' }
|
108
|
+
inspec> end
|
109
|
+
inspec> end
|
110
|
+
✔ my_control: Environment variable HOME content should eq "/Users/ksubramanian"
|
111
|
+
|
112
|
+
Summary: 1 successful, 0 failures, 0 skipped
|
113
|
+
```
|
114
|
+
|
115
|
+
Syntax errors are illegal tests are also detected and reported.
|
116
|
+
|
117
|
+
```bash
|
118
|
+
inspec> control 'foo' do
|
119
|
+
inspec> thisisnonsense
|
120
|
+
inspec> end
|
121
|
+
NameError: undefined local variable or method `thisisnonsense' for #<#<Class:0x007fd63b571f98>:0x007fd639825cc8>
|
122
|
+
from /usr/local/lib/ruby/gems/2.3.0/gems/rspec-expectations-3.5.0/lib/rspec/matchers.rb:967:in `method_missing'
|
123
|
+
inspec> control 'foo' do
|
124
|
+
inspec> describe file('wut') do
|
125
|
+
inspec> its('thismakesnosense') { should cmp 'fail' }
|
126
|
+
inspec> end
|
127
|
+
inspec> end
|
128
|
+
✖ foo: File wut thismakesnosense (undefined method `thismakesnosense' for File wut:Inspec::Resource::Registry::File)
|
129
|
+
|
130
|
+
Summary: 0 successful, 1 failures, 0 skipped
|
131
|
+
```
|
132
|
+
|
133
|
+
## Running a single InSpec command
|
134
|
+
|
135
|
+
If you wish to run a single InSpec command and fetch its results, you
|
136
|
+
may use the `-c` flag. This is similar to using `bash -c`.
|
137
|
+
|
138
|
+
```bash
|
139
|
+
$ inspec shell -c 'describe file("/Users/ksubramanian") do it { should exist } end'}
|
140
|
+
Target: local://
|
141
|
+
|
142
|
+
✔ File /Users/ksubramanian should exist
|
143
|
+
|
144
|
+
Summary: 1 successful, 0 failures, 0 skipped
|
145
|
+
```
|
146
|
+
|
147
|
+
```bash
|
148
|
+
$ inspec shell --format json -c 'describe file("/Users/ksubramanian") do it { should exist } end'
|
149
|
+
{"version":"0.30.0","profiles":{"":{"supports":[],"controls":{"(generated from in_memory.rb:1 5aab65c33fb1f133d9244017958eef64)":{"title":null,"desc":null,"impact":0.5,"refs":[],"tags":{},"code":" rule = rule_class.new(id, profile_id, {}) do\n res = describe(*args, &block)\n end\n","source_location":{"ref":"/Users/ksubramanian/repo/chef/inspec/lib/inspec/profile_context.rb","line":184},"results":[{"status":"passed","code_desc":"File /Users/ksubramanian should exist","run_time":0.000747,"start_time":"2016-08-16 11:41:40 -0400"}]}},"groups":{"in_memory.rb":{"title":null,"controls":["(generated from in_memory.rb:1 5aab65c33fb1f133d9244017958eef64)"]}},"attributes":[]}},"other_checks":[],"summary":{"duration":0.001078,"example_count":1,"failure_count":0,"skip_count":0}}}
|
150
|
+
```
|
data/inspec.gemspec
CHANGED
@@ -24,7 +24,7 @@ Gem::Specification.new do |spec|
|
|
24
24
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
25
25
|
spec.require_paths = ['lib']
|
26
26
|
|
27
|
-
spec.add_dependency 'train', '>=0.19.
|
27
|
+
spec.add_dependency 'train', '>=0.19.1', '<1.0'
|
28
28
|
spec.add_dependency 'thor', '~> 0.19'
|
29
29
|
spec.add_dependency 'json', '>= 1.8', '< 3.0'
|
30
30
|
spec.add_dependency 'rainbow', '~> 2'
|
@@ -9,18 +9,6 @@ module Compliance
|
|
9
9
|
# API Implementation does not hold any state by itself,
|
10
10
|
# everything will be stored in local Configuration store
|
11
11
|
class API
|
12
|
-
# login method for pre-1.0 compliance server
|
13
|
-
def self.legacy_login_post(url, username, password, insecure)
|
14
|
-
# form request
|
15
|
-
# TODO: reuse post function
|
16
|
-
uri = URI.parse(url)
|
17
|
-
req = Net::HTTP::Post.new(uri.path)
|
18
|
-
req.basic_auth(username, password)
|
19
|
-
req.form_data={}
|
20
|
-
|
21
|
-
send_request(uri, req, insecure)
|
22
|
-
end
|
23
|
-
|
24
12
|
# return all compliance profiles available for the user
|
25
13
|
def self.profiles(config)
|
26
14
|
url = "#{config['server']}/user/compliance"
|
@@ -86,19 +74,19 @@ Please login using `inspec compliance login https://compliance.test --user admin
|
|
86
74
|
[res.is_a?(Net::HTTPSuccess), res.body]
|
87
75
|
end
|
88
76
|
|
89
|
-
|
77
|
+
# Use username and refresh_toke to get an API access token
|
78
|
+
def self.get_token_via_refresh_token(url, refresh_token, insecure)
|
90
79
|
uri = URI.parse("#{url}/login")
|
91
80
|
req = Net::HTTP::Post.new(uri.path)
|
92
|
-
|
93
|
-
req.body = { token: token }.to_json
|
81
|
+
req.body = { token: refresh_token }.to_json
|
94
82
|
access_token = nil
|
95
83
|
response = Compliance::HTTP.send_request(uri, req, insecure)
|
96
84
|
data = response.body
|
97
|
-
if
|
85
|
+
if response.code == '200'
|
98
86
|
begin
|
99
87
|
tokendata = JSON.parse(data)
|
100
88
|
access_token = tokendata['access_token']
|
101
|
-
msg = 'Successfully fetched access token'
|
89
|
+
msg = 'Successfully fetched API access token'
|
102
90
|
success = true
|
103
91
|
rescue JSON::ParserError => e
|
104
92
|
success = false
|
@@ -106,7 +94,29 @@ Please login using `inspec compliance login https://compliance.test --user admin
|
|
106
94
|
end
|
107
95
|
else
|
108
96
|
success = false
|
109
|
-
msg =
|
97
|
+
msg = "Failed to authenticate to #{url} \n\
|
98
|
+
Response code: #{response.code}\n Body: #{response.body}"
|
99
|
+
end
|
100
|
+
|
101
|
+
[success, msg, access_token]
|
102
|
+
end
|
103
|
+
|
104
|
+
# Use username and password to get an API access token
|
105
|
+
def self.get_token_via_password(url, username, password, insecure)
|
106
|
+
uri = URI.parse("#{url}/login")
|
107
|
+
req = Net::HTTP::Post.new(uri.path)
|
108
|
+
req.body = { userid: username, password: password }.to_json
|
109
|
+
access_token = nil
|
110
|
+
response = Compliance::HTTP.send_request(uri, req, insecure)
|
111
|
+
data = response.body
|
112
|
+
if response.code == '200'
|
113
|
+
access_token = data
|
114
|
+
msg = 'Successfully fetched an API access token valid for 12 hours'
|
115
|
+
success = true
|
116
|
+
else
|
117
|
+
success = false
|
118
|
+
msg = "Failed to authenticate to #{url} \n\
|
119
|
+
Response code: #{response.code}\n Body: #{response.body}"
|
110
120
|
end
|
111
121
|
|
112
122
|
[success, msg, access_token]
|
@@ -22,9 +22,9 @@ module Compliance
|
|
22
22
|
option :insecure, aliases: :k, type: :boolean,
|
23
23
|
desc: 'Explicitly allows InSpec to perform "insecure" SSL connections and transfers'
|
24
24
|
option :user, type: :string, required: false,
|
25
|
-
desc: 'Chef Compliance Username
|
25
|
+
desc: 'Chef Compliance Username'
|
26
26
|
option :password, type: :string, required: false,
|
27
|
-
desc: 'Chef Compliance Password
|
27
|
+
desc: 'Chef Compliance Password'
|
28
28
|
option :apipath, type: :string, default: '/api',
|
29
29
|
desc: 'Set the path to the API, defaults to /api'
|
30
30
|
option :token, type: :string, required: false,
|
@@ -38,7 +38,7 @@ module Compliance
|
|
38
38
|
url = options['server'] + options['apipath']
|
39
39
|
if !options['user'].nil? && !options['password'].nil?
|
40
40
|
# username / password
|
41
|
-
_success, msg =
|
41
|
+
_success, msg = login_username_password(url, options['user'], options['password'], options['insecure'])
|
42
42
|
elsif !options['user'].nil? && !options['token'].nil?
|
43
43
|
# access token
|
44
44
|
_success, msg = store_access_token(url, options['user'], options['token'], options['insecure'])
|
@@ -199,7 +199,7 @@ module Compliance
|
|
199
199
|
private
|
200
200
|
|
201
201
|
def login_refreshtoken(url, options)
|
202
|
-
success, msg, access_token = Compliance::API.
|
202
|
+
success, msg, access_token = Compliance::API.get_token_via_refresh_token(url, options['refresh_token'], options['insecure'])
|
203
203
|
if success
|
204
204
|
config = Compliance::Configuration.new
|
205
205
|
config['server'] = url
|
@@ -212,25 +212,17 @@ module Compliance
|
|
212
212
|
[success, msg]
|
213
213
|
end
|
214
214
|
|
215
|
-
def
|
215
|
+
def login_username_password(url, username, password, insecure)
|
216
216
|
config = Compliance::Configuration.new
|
217
|
-
success,
|
218
|
-
if
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
config.store
|
227
|
-
success = true
|
228
|
-
msg = 'Successfully authenticated'
|
229
|
-
else
|
230
|
-
msg = 'Response does not include a token'
|
231
|
-
end
|
232
|
-
else
|
233
|
-
msg = "Authentication failed for Server: #{url}"
|
217
|
+
success, msg, api_token = Compliance::API.get_token_via_password(url, username, password, insecure)
|
218
|
+
if success
|
219
|
+
config['server'] = url
|
220
|
+
config['user'] = username
|
221
|
+
config['token'] = api_token
|
222
|
+
config['insecure'] = insecure
|
223
|
+
config['version'] = Compliance::API.version(url, insecure)
|
224
|
+
config.store
|
225
|
+
success = true
|
234
226
|
end
|
235
227
|
[success, msg]
|
236
228
|
end
|
@@ -245,10 +237,10 @@ module Compliance
|
|
245
237
|
config['version'] = Compliance::API.version(url, insecure)
|
246
238
|
config.store
|
247
239
|
|
248
|
-
[true, 'access token stored']
|
240
|
+
[true, 'API access token stored']
|
249
241
|
end
|
250
242
|
|
251
|
-
# saves
|
243
|
+
# saves a refresh token supplied by the user
|
252
244
|
def store_refresh_token(url, refresh_token, verify, user, insecure)
|
253
245
|
config = Compliance::Configuration.new
|
254
246
|
config['server'] = url
|
@@ -260,13 +252,13 @@ module Compliance
|
|
260
252
|
if !verify
|
261
253
|
config.store
|
262
254
|
success = true
|
263
|
-
msg = 'refresh token stored'
|
255
|
+
msg = 'API refresh token stored'
|
264
256
|
else
|
265
|
-
success, msg, access_token = Compliance::API.
|
257
|
+
success, msg, access_token = Compliance::API.get_token_via_refresh_token(url, refresh_token, insecure)
|
266
258
|
if success
|
267
259
|
config['token'] = access_token
|
268
260
|
config.store
|
269
|
-
msg = 'token verified and stored'
|
261
|
+
msg = 'API access token verified and stored'
|
270
262
|
end
|
271
263
|
end
|
272
264
|
|
data/lib/fetchers/git.rb
CHANGED
data/lib/fetchers/local.rb
CHANGED
@@ -55,8 +55,23 @@ module Fetchers
|
|
55
55
|
@target
|
56
56
|
end
|
57
57
|
|
58
|
+
def writable?
|
59
|
+
File.directory?(@target)
|
60
|
+
end
|
61
|
+
|
62
|
+
def cache_key
|
63
|
+
sha256.to_s
|
64
|
+
end
|
65
|
+
|
66
|
+
def sha256
|
67
|
+
return nil if File.directory?(@target)
|
68
|
+
@archive_shasum ||= Digest::SHA256.hexdigest File.read(@target)
|
69
|
+
end
|
70
|
+
|
58
71
|
def resolved_source
|
59
|
-
{ path: @target }
|
72
|
+
h = { path: @target }
|
73
|
+
h[:sha256] = sha256 if sha256
|
74
|
+
h
|
60
75
|
end
|
61
76
|
end
|
62
77
|
end
|
data/lib/fetchers/mock.rb
CHANGED
data/lib/fetchers/url.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
# author: Christoph Hartmann
|
4
4
|
|
5
5
|
require 'uri'
|
6
|
+
require 'digest'
|
6
7
|
require 'tempfile'
|
7
8
|
require 'open-uri'
|
8
9
|
|
@@ -81,34 +82,61 @@ module Fetchers
|
|
81
82
|
end
|
82
83
|
|
83
84
|
def fetch(path)
|
84
|
-
|
85
|
-
|
85
|
+
@archive_path ||= download_archive(path)
|
86
|
+
end
|
87
|
+
|
88
|
+
def sha256
|
89
|
+
c = if @archive_path
|
90
|
+
File.read(@archive_path)
|
91
|
+
else
|
92
|
+
content
|
93
|
+
end
|
94
|
+
Digest::SHA256.hexdigest c
|
86
95
|
end
|
87
96
|
|
88
97
|
def resolved_source
|
89
|
-
{ url: @target }
|
98
|
+
@resolved_source ||= { url: @target, sha256: sha256 }
|
99
|
+
end
|
100
|
+
|
101
|
+
def cache_key
|
102
|
+
sha256
|
103
|
+
end
|
104
|
+
|
105
|
+
def to_s
|
106
|
+
@target
|
90
107
|
end
|
91
108
|
|
92
109
|
private
|
93
110
|
|
94
|
-
|
95
|
-
|
96
|
-
def download_archive(path)
|
111
|
+
def open_target
|
112
|
+
Inspec::Log.debug("Fetching URL: #{@target}")
|
97
113
|
http_opts = {}
|
98
114
|
http_opts['ssl_verify_mode'.to_sym] = OpenSSL::SSL::VERIFY_NONE if @insecure
|
99
115
|
http_opts['Authorization'] = "Bearer #{@token}" if @token
|
116
|
+
open(@target, http_opts)
|
117
|
+
end
|
100
118
|
|
101
|
-
|
119
|
+
def content
|
120
|
+
open_target.read
|
121
|
+
end
|
102
122
|
|
123
|
+
def file_type_from_remote(remote)
|
103
124
|
content_type = remote.meta['content-type']
|
104
|
-
file_type = MIME_TYPES[content_type]
|
105
|
-
throw(RuntimeError, 'Failed to resolve URL target, its '\
|
106
|
-
"metadata did not match ZIP or TAR: #{content_type}")
|
125
|
+
file_type = MIME_TYPES[content_type]
|
107
126
|
|
108
|
-
# fall back to tar
|
109
127
|
if file_type.nil?
|
110
|
-
|
128
|
+
Inspec::Log.warn("Unrecognized content type: #{content_type}. Assuming tar.gz")
|
129
|
+
file_type = '.tar.gz'
|
111
130
|
end
|
131
|
+
|
132
|
+
file_type
|
133
|
+
end
|
134
|
+
|
135
|
+
# download url into archive using opts,
|
136
|
+
# returns File object and content-type from HTTP headers
|
137
|
+
def download_archive(path)
|
138
|
+
remote = open_target
|
139
|
+
file_type = file_type_from_remote(remote)
|
112
140
|
final_path = "#{path}#{file_type}"
|
113
141
|
# download content
|
114
142
|
archive = Tempfile.new(['inspec-dl-', file_type])
|